diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 6f651076ce..fd3455ac76 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,12 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.18.2-nightly.2 + - 3.18.2-nightly.1 + - 3.18.1 + - 3.18.1-nightly.1 + - 3.18.0 + - 3.17.7 - 3.17.7-nightly.7 - 3.17.7-nightly.6 - 3.17.7-nightly.5 @@ -129,12 +135,6 @@ body: - 3.15.4 - 3.15.4-nightly.3 - 3.15.4-nightly.2 - - 3.15.4-nightly.1 - - 3.15.3 - - 3.15.3-nightly.4 - - 3.15.3-nightly.3 - - 3.15.3-nightly.2 - - 3.15.3-nightly.1 validations: required: true - type: dropdown diff --git a/CHANGELOG.md b/CHANGELOG.md index 5909c26f7e..f309d904eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,1146 @@ # Changelog +## [3.18.1](https://github.com/ynput/OpenPype/tree/3.18.1) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.18.0...3.18.1) + +### **πŸš€ Enhancements** + + +
+AYON: Update ayon api to 1.0.0-rc.3 #6052 + +Updated ayon python api to 1.0.0-rc.3. + + +___ + +
+ + + + +## [3.18.0](https://github.com/ynput/OpenPype/tree/3.18.0) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/...3.18.0) + +### **πŸ› Bug fixes** + + +
+Chore: Fix subst paths handling #5702 + +Make sure that source disk ends with `\` instead of destination disk. + + +___ + +
+ + + + +## [3.17.7](https://github.com/ynput/OpenPype/tree/3.17.7) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.17.6...3.17.7) + +### **πŸ†• New features** + + +
+AYON: Use folder path as unique identifier #5817 + +Use folder path instead of asset name as unique identifier, with OpenPype compatibility. + + +___ + +
+ + +
+Houdini: Farm caching submission to Deadline #4903 + +Implements functionality to offload instances of the specific families to be processed on Deadline instead of locally. This increases productivity as artist can use local machine could be used for other tasks.Implemented for families: +- [x] ass +- [x] redshift proxy +- [x] ifd +- [x] abc +- [x] bgeo +- [x] vdb + + +___ + +
+ + +
+Houdini: Add support to split Deadline render tasks in export + render #5420 + +This adds initial support in Houdini so when submitting render jobs to Deadline it's not running as a single Houdini task but rather it gets split in two different tasks: Export + Render. This way it's more efficient as we only need a Houdini license during the export step and the render tasks can run exclusively with a render license. Moreover, we aren't wasting all the overhead time of opening the render scene in Houdini for every frame.I have also added the corresponding settings json files so we can set some of the default values for the Houdini deadline submitter. + + +___ + +
+ + +
+Wrap: new integration #5823 + +These modifications are necessary for adding Wrap integration (DCC handling scans and textures) . + + +___ + +
+ + +
+AYON: Prepare for 'data' via graphql #5923 + +AYON server does support to query 'data' field for hierarchy entities (project > ... > representation) using GraphQl since version 0.5.5. Because of this PR in ayon-python-api it is required to modify custom graphql function in `openpype.client` to support that option. + + +___ + +
+ + +
+Chore AYON: AYON addon class #5937 + +Introduced base class for AYON addon in openpype modules discovery logic. + + +___ + +
+ + +
+Asset Usage Reporter Tool #5946 + +This adds simple tool for OpenPype mode that will go over all published workfiles and print linked assets and their version:This is created per project and can be exported in csv file or copied to clipboard in _"ASCII Human readable form"_. + + +___ + +
+ + +
+Testing: dump_databases flag #5955 + +This introduces a `dump_databases` flag which makes it convenient to output the resulting database of a successful test run. The flag supports two formats; `bson` and `json`.Due to outputting to the test data folder, when dumping the databases, the test data folder will persist.Split from https://github.com/ynput/OpenPype/pull/5644 + + +___ + +
+ + +
+SiteSync: implemented in Ayon Loader #5962 + +Implemented `Availability` column in Ayon loader and redo of loaders to `ActionItems` in representation window there. + + +___ + +
+ + +
+AYON: Workfile template build works #5975 + +Modified workfile template builder to work, to some degree, in AYON mode. + + +___ + +
+ +### **πŸš€ Enhancements** + + +
+Maya: Small Tweaks on Validator for Look Default Shader Connection for Maya 2024 #5957 + +Resolve https://github.com/ynput/OpenPype/issues/5269 + + +___ + +
+ + +
+Settings: Changes in default settings #5983 + +We've made some changes in the default settings as several application versions were obsolete (Maya 18, Nuke 11, PS 2020, etc). Also added tools and changed settings for Blender, Maya, and Blender. + +All should work as usual. +___ + +
+ + +
+Testing: Do not persist data by default in Maya/Deadline. #5987 + +This is similar to the Maya publishing test. + + +___ + +
+ + +
+Max: Validate loaded plugins tweaks #5820 + +In the current development of 3dsMax, users need to use separate validators to validate if certain plugins being loaded before the extraction. For example, usd extractor in model family, prt/tycache extractor in pointcloud/tycache family.But with the PR where implements optional validate loaded plugin, users just need to put what kind of plugins they want to validate in the settings. They no longer need to go through all the separate plugin validators when publishing, and only one validator would do all the check on the loaded plugins before extraction. + + +___ + +
+ + +
+Nuke: Change context label enhancement #5887 + +Use QAction to change label of context label in Nuke pipeline menu. + + +___ + +
+ + +
+Chore: Do not use template data as source for context #5918 + +Use available information on context to receive context data instead of using `"anatomyData"` during publishing. + + +___ + +
+ + +
+Houdini: Add python3.10 libs for Houdini 20 startup #5932 + +Add python3.10 libs for Houdini 20 startup + + +___ + +
+ + +
+General: Use colorspace data when creating thumbnail #5938 + +Thumbnails with applied colormanagement. + + +___ + +
+ + +
+Ftrack: rewriting component creation to support multiple thumbnails #5939 + +The creation of Ftrack components needs to allow for multiple thumbnails. This is important in situations where there could be several reviewable streams, like in the case of a nuke intermediate files preset. Customers have asked for unique thumbnails for each data stream.For instance, one stream might contain a baked LUT file along with Display and View. Another stream might only include the baked Display and View. These variations can change the overall look. Thus, we found it necessary to depict these differences via thumbnails. + + +___ + +
+ + +
+Chore: PySide6 tree view style #5940 + +Define solid color for background of branch in QTreeView. + + +___ + +
+ + +
+Nuke: Explicit Thumbnail workflow #5941 + +Nuke made a shift from using its own plugin to a global one for thumbnail creation. This was because it had to handle several thumbnail workflows for baking intermediate data streams. To manage this, the global plugin had to be upgraded. Now, each baking stream can set a unique tag 'need_thumbnail'. This tag is used to mark representations that need a thumbnail. + + +___ + +
+ + +
+Global: extract thumbnail with new settings #5944 + +Settings are now configurable for the following: +- target size of thumbnail - source or constrained to specific +- where should be frame taken from in sequence or video file +- if thumbnail should be integrated or not +- background color for letter boxes +- added AYON settings + + +___ + +
+ + +
+RoyalRender: inject submitter environment to the royal render job #5958 + +This is an attempt to solve runtime environment injection for render jobs in RoyalRender as there is no easy way to implement something like `GlobalJobPreload` logic in Deadline. Idea is to inject OpenPype environments directly to the job itself. + + +___ + +
+ + +
+General: Use manual thumbnail if present when publishing #5969 + +Use manual thumbnail added to the publisher instead of using it from published representation. + + +___ + +
+ + +
+AYON: Change of server url should work as expected #5971 + +Using login action in tray menu to change server url should correctly start new process without issues of missing bundle or previous url. + + +___ + +
+ + +
+AYON: make sure the AYON menu bar in 3dsMax is named AYON when AYON launches #5972 + +Renaming the menu bar in 3dsMax for AYON and some cosmetic fix in the docstring + + +___ + +
+ + +
+Resolve: renaming menu to AYON #5974 + +Resolve in Ayon is now having aligned name. + + +___ + +
+ + +
+Hiero: custom tools menu rename #5976 + +- OpenPype Tools are now Custom Tools menu +- fixing order of tools. Create should be first. + + +___ + +
+ + +
+nuke: updating name for custom tools menu item #5977 + +- Ayon variant of settings renamed `Custom Tools` menu item + + +___ + +
+ + +
+fusion: AYON renaming menu #5978 + +Fusion is having Ayon menu. + + +___ + +
+ + +
+Blender: Changed the labels for Layout JSON Extractor #5981 + +Changed the labels for Blender's Layout JSON Extractor. + + +___ + +
+ + +
+Testing: Skip Arnold license for test rendering. #5984 + +Skip license check when rendering for testing. + + +___ + +
+ + +
+Testing: Validate errors and failed status from Deadline jobs. #5986 + +While waiting for the Deadline jobs to finish, we query the errors on the job and its dependent jobs to fail as early as possible. Plus the failed status. + + +___ + +
+ + +
+AYON: rename Openpype Tools as Custom Tools in Maya Host #5991 + +Rename Openpype Tools as Custom Tools in Maya Host in + + +___ + +
+ + +
+AYON: Use AYON label in ayon mode #5995 + +Replaced OpenPype with AYON in AYON mode and added bundle nam to information. + + +___ + +
+ + +
+AYON: Update ayon python api #6002 + +Updated ayon-python-api to '1.0.0-rc.1'. + + +___ + +
+ + +
+Max: Add missing repair action in validate resolution setting #6014 + +Add missing repair action for validate resolution setting + + +___ + +
+ + +
+Add the AYON/OP settings to enable extractor for model family in 3dsmax #6027 + +Add the AYON/OP settings to enable extractor for model family in 3dsmax + + +___ + +
+ + +
+Bugfix: Fix error message formatting if ayon executable can't be found by deadline #6028 + +Without this fix the error message would report executables string with `;` between EACH character, similar to this PR: https://github.com/ynput/OpenPype/pull/5815However that PR apparently missed also fixing it in `GlobalJobPreLoad` and only fixed it in `Ayon.py` plugin. + + +___ + +
+ + +
+Show slightly different info in AYON mode #6031 + +This PR changes what is shown in Tray menu in AYON mode. Previously, it showed version of OpenPype that is very confusing in AYON mode. So this now shows AYON version instead. When clicked, it will opene AYON info window, where OpenPype version is now added, for debugging purposes. + + +___ + +
+ + +
+AYON Editorial: Hierarchy context have names as keys #6041 + +Use folder name as keys in `hierarchyContext` and modify hierachy extraction accordingly. + + +___ + +
+ + +
+AYON: Convert the createAt value to local timezone #6043 + +Show correct create time in UIs. + + +___ + +
+ +### **πŸ› Bug fixes** + + +
+Maya: Render creation - fix broken imports #5893 + +Maya specific imports were moved to specific methods but not in all cases by #5775. This is just quickly restoring functionality without questioning that decision. + + +___ + +
+ + +
+Maya: fix crashing model renderset collector #5929 + +This fix is handling case where model is in some type of render sets but no other connections are made there. Publishing this model would fail with `RuntimeError: Found no items to list the history for.` + + +___ + +
+ + +
+Maya: Remove duplicated attributes of MTOA verbosity level #5945 + +Remove duplicated attributes implementation mentioned in https://github.com/ynput/OpenPype/pull/5931#discussion_r1402175289 + + +___ + +
+ + +
+Maya: Bug fix Redshift Proxy not being successfully published #5956 + +Bug fix redshift proxy family not being successfully published due to the error found in integrate.py + + +___ + +
+ + +
+Maya: Bug fix load image for texturesetMain #6011 + +Bug fix load image with file node for texturesetMain + + +___ + +
+ + +
+Maya: bug fix the repair function in validate_rendersettings #6021 + +The following error has been encountered below: +``` +// pyblish.pyblish.plugin.Action : Finding failed instances.. +// pyblish.pyblish.plugin.Action : Attempting repair for instance: renderLookdevMain ... +// Error: pyblish.plugin : Traceback (most recent call last): +// File "C:\Users\lbate\AppData\Local\Ynput\AYON\dependency_packages\ayon_2310271602_windows.zip\dependencies\pyblish\plugin.py", line 527, in __explicit_process +// runner(*args) +// File "C:\Users\lbate\AppData\Local\Ynput\AYON\addons\openpype_3.17.7-nightly.6\openpype\pipeline\publish\publish_plugins.py", line 241, in process +// plugin.repair(instance) +// File "C:\Users\lbate\AppData\Local\Ynput\AYON\addons\openpype_3.17.7-nightly.6\openpype\hosts\maya\plugins\publish\validate_rendersettings.py", line 395, in repair +// cmds.setAttr("{}.{}".format(node, prefix_attr), +// UnboundLocalError: local variable 'node' referenced before assignment +// Traceback (most recent call last): +// File "C:\Users\lbate\AppData\Local\Ynput\AYON\dependency_packages\ayon_2310271602_windows.zip\dependencies\pyblish\plugin.py", line 527, in __explicit_process +// runner(*args) +// File "C:\Users\lbate\AppData\Local\Ynput\AYON\addons\openpype_3.17.7-nightly.6\openpype\pipeline\publish\publish_plugins.py", line 241, in process +// plugin.repair(instance) +// File "C:\Users\lbate\AppData\Local\Ynput\AYON\addons\openpype_3.17.7-nightly.6\openpype\hosts\maya\plugins\publish\validate_rendersettings.py", line 395, in repair +// cmds.setAttr("{}.{}".format(node, prefix_attr), +// UnboundLocalError: local variable 'node' referenced before assignment +``` +This PR is a fix for that + + +___ + +
+ + +
+Fusion: Render avoid unhashable type `BlackmagicFusion.PyRemoteObject` error #5672 + +Fix Fusion 18.6+ support: Avoid issues with Fusion's `BlackmagicFusion.PyRemoteObject` instances being unhashable. +```python +Traceback (most recent call last): + File "E:\openpype\OpenPype\.venv\lib\site-packages\pyblish\plugin.py", line 527, in __explicit_process + runner(*args) + File "E:\openpype\OpenPype\openpype\hosts\fusion\plugins\publish\extract_render_local.py", line 61, in process + result = self.render(instance) + File "E:\openpype\OpenPype\openpype\hosts\fusion\plugins\publish\extract_render_local.py", line 118, in render + with enabled_savers(current_comp, savers_to_render): + File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\contextlib.py", line 119, in __enter__ + return next(self.gen) + File "E:\openpype\OpenPype\openpype\hosts\fusion\plugins\publish\extract_render_local.py", line 33, in enabled_savers + original_states[saver] = original_state +TypeError: unhashable type: 'BlackmagicFusion.PyRemoteObject' +``` + + + +___ + +
+ + +
+Nuke: Validate Nuke Write Nodes refactor to use variable `node_value` instead of `value` #5764 + +Nuke: Validate Nuke Write Nodes refactor to use variable `node_value` instead of `value`The variable `value` only exists as the last variable value in the `for value in values` loop and might not be declared if `values` is an empty iterable. + + +___ + +
+ + +
+resolve: fixing loader handles calculation #5863 + +Resolve was not correctly calculating duration of database related duration. + + +___ + +
+ + +
+Chore: Staging mode determination #5895 + +Resources use `is_staging_enabled` function instead of `is_running_staging` to determine if should use staging icon. And fixed comparison bug in `is_running_staging`. + + +___ + +
+ + +
+AYON: Handle staging templates category #5905 + +Staging anatomy templates category is handled during project templates conversion. The keys are stored into `others` with `"staging_"` prefix. + + +___ + +
+ + +
+Max: fix the subset name not changing accordingly after the variant name changes #5911 + +Resolve #5902 + + +___ + +
+ + +
+AYON: Loader tool bugs hunt #5915 + +Fix issues with invalid representation ids in loaded containers and handle missing product type in server database. + + +___ + +
+ + +
+Publisher: Bugfixes and enhancements #5924 + +Small fixes/enhancements in publisher UI. + + +___ + +
+ + +
+Maya: Supports for additional Job Info and Plugin Info in deadline submission #5931 + +This PR is to resolve some of the attributes such as MTOA's `ArnoldVerbose` are not preserved on farm and users can use the project settings to add the attributes back to either job or plugin Info. + + +___ + +
+ + +
+Bugfix: Houdini license validator missing families #5934 + +Adding missing families to Houdini license validator. + + +___ + +
+ + +
+TrayPublisher: adding back `asset_doc` variable #5943 + +Returning variable which had been removed accidentally in previous PR. + + +___ + +
+ + +
+Settings: Fix ModulesManager init args #5947 + +Remove usage of kwargs to create ModulesManager. + + +___ + +
+ + +
+Blender: Fix Deadline Frames per task #5949 + +Fixed a problem with Frames per task setting not being applied when publishing a render. + + +___ + +
+ + +
+Testing: Fix is_test_failed #5951 + +`is_test_failed` is used (exclusively) on module fixtures to determine whether the tests have failed or not. This determines whether to run tear down code like cleaning up the database and temporary files.But in the module scope `request.node.rep_call` is not available, which results in `is_test_failed` always returning `True`, and no tear down code get executed.The solution was taken from; https://github.com/pytest-dev/pytest/issues/5090 + + +___ + +
+ + +
+Harmony: Fix local rendering #5953 + +Local rendering was throwing warning about license, but didn't fail per se. It just didn't produce anything. + + +___ + +
+ + +
+Testing: hou module should be within class code. #5954 + +`hou` module should be within the class code else we'll get pyblish errors from needing to skip the plugin. + + +___ + +
+ + +
+Maya: Add Label to MayaUSDReferenceLoader #5964 + +As the create placeholder dialog displays the two distinct loaders with the same name, this PR is to distinguish Maya USD Reference Loaders from the loaders of which inherited from. See the screenshot below: + + +___ + +
+ + +
+Max: Bug fix the resolution not being shown correctly in review burnin #5965 + +The resolution is not being shown correctly in review burnin + + +___ + +
+ + +
+AYON: Fix thumbnail integration #5970 + +Thumbnail integration could cause crash of server if thumbnail id was changed for the same entity id multiple times. Modified the code to avoid that issue. + + +___ + +
+ + +
+Photoshop: Updated label in Settings #5980 + +Replaced wrong label from different plugin. + + +___ + +
+ + +
+Photoshop: Fix removed unsupported Path #5996 + +Path is not json serializable by default, it is not necessary, better model reused. + + +___ + +
+ + +
+AYON: Prepare functions for newer ayon-python-api #5997 + +Newer ayon python api will add new filtering options or change order of existing. Kwargs are used in client code to prevent issues on update. + + +___ + +
+ + +
+AYON: Conversion of the new playblast settings in Maya #6000 + +Conversion of the new playblast settings in Maya + + +___ + +
+ + +
+AYON: Bug fix for loading Mesh in Substance Painter as new project not working #6004 + +Substance Painter in AYON can't load mesh for creating a new project + + +___ + +
+ + +
+Deadline: correct webservice couldn't be selected in Ayon #6007 + +Changed the Setting model to mimic more OP approach as it needs to live together for time being. + + +___ + +
+ + +
+AYON tools: Fix refresh thread #6008 + +Trigger 'refresh_finished' signal out of 'run' method. + + +___ + +
+ + +
+Ftrack: multiple reviewable components missing variable #6013 + +Missing variable in code for editorial publishing in traypublisher. + + +___ + +
+ + +
+TVPaint: Expect legacy instances in metadata #6015 + +Do not expect `"workfileInstances"` constains only new type instance data with `creator_identifier`. + + +___ + +
+ + +
+Bugfix: handle missing key in Deadline #6019 + +This quickly fixes bug introduced by #5420 + + +___ + +
+ + +
+Revert `extractenvironments` behaviour #6020 + +This is returning original behaviour of `extractenvironments` command from before #5958 so we restore functionality. + + +___ + +
+ + +
+OP-7535 - Fix renaming composition in AE #6025 + +Removing of `render` instance caused renaming of composition to `dummyComp` which caused issue in publishing in next attempt.This PR stores original composition name(cleaned up for product name creation) and uses it if instance needs to be removed. + + +___ + +
+ + +
+Refactor code to skip instance creation for new assets #6029 + +Publishing effects from hiero during editorial publish is working as expected again. + + +___ + +
+ + +
+Refactor code to handle missing "representations" key in instance data #6032 + +Minor code change for optimisation of thumbnail workflow. + + +___ + +
+ + +
+Traypublisher: editorial preserve clip case sensitivity #6036 + +Keep EDL clip name inheritance with case sensitivity. + + +___ + +
+ + +
+Bugfix/add missing houdini settings #6039 + +add missing settings. now, it looks like this:| Ayon | OpenPype || -- | -- | | | || | | + + +___ + +
+ +### **πŸ”€ Refactored code** + + +
+Maya: Remove RenderSetup layer observers #5836 + +Remove RenderSetup layer observers that are not needed since new publisher since Renderlayer Creators manage these themselves on Collect and Save/Update of instances. + + +___ + +
+ +### **Merged pull requests** + + +
+Tests: Removed render instance #6026 + +This test was created as simple model and workfile publish, without Deadline rendering. Cleaned up render elements. + + +___ + +
+ + +
+Tests: update after thumbnail default change #6040 + +https://github.com/ynput/OpenPype/pull/5944 changed default state of integration of Thumbnails to NOT integrate. This PR updates automatic tests to follow that. + + +___ + +
+ + +
+Houdini: Remove legacy LOPs USD output processors #5861 + +Remove unused/broken legacy code for Houdini Solaris USD LOPs output processors. The code was originally written in Avalon, against early Houdini 18 betas which had a different API for output processors and thus the current state doesn't even work in recent versions of Houdini. + + +___ + +
+ + +
+Chore: Substance Painter Addons for Ayon #5914 + +Substance Painter Addons for Ayon + + +___ + +
+ + +
+Ayon: Updated name of Adobe extension to Ayon #5992 + +This changes name in menu in Adobe extensions to Ayon. + + +___ + +
+ + +
+Chore/houdini update startup log #6003 + +print `Installing AYON ...` on startup when launching houdini from launcher in ayon mode.also update submenu to `ayon_menu` instead of `openpype_menu` + + +___ + +
+ + +
+Revert "Ayon: Updated name of Adobe extension to Ayon" #6010 + +Reverts ynput/OpenPype#5992 + +That PR is only applicable to Ayon. +___ + +
+ + +
+Standalone/Tray Publisher: Remove simple Unreal texture publishing #6012 + +We are removing _simple Unreal Texture publishing_ that was just renaming texture files to fit to Unreal naming conventions but without any additional functionality. We might return this functionality back with better texture publishing system.Related to #5983 + + +___ + +
+ + +
+Deadline: Bump version because of Settings changes for Deadline #6023 + + +___ + +
+ + +
+Change ASCII art in the Console based on the server mode #6030 + +This changes ASCII art in the console based on the AYON/OpenPype mode + + +___ + +
+ + + + ## [3.17.6](https://github.com/ynput/OpenPype/tree/3.17.6) diff --git a/README.md b/README.md index ed3e058002..a79b9f2582 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@ OpenPype [![documentation](https://github.com/pypeclub/pype/actions/workflows/documentation.yml/badge.svg)](https://github.com/pypeclub/pype/actions/workflows/documentation.yml) ![GitHub VFX Platform](https://img.shields.io/badge/vfx%20platform-2022-lightgrey?labelColor=303846) +## Important Notice! + +OpenPype as a standalone product has reach end of it's life and this repository is now used as a pipeline core code for [AYON](https://ynput.io/ayon/). You can read more details about the end of life process here https://community.ynput.io/t/openpype-end-of-life-timeline/877 + Introduction ------------ diff --git a/openpype/client/server/conversion_utils.py b/openpype/client/server/conversion_utils.py index 51af99e722..e8d3c4cbe4 100644 --- a/openpype/client/server/conversion_utils.py +++ b/openpype/client/server/conversion_utils.py @@ -606,7 +606,7 @@ def convert_v4_version_to_v3(version): output_data[dst_key] = version[src_key] if "createdAt" in version: - created_at = arrow.get(version["createdAt"]) + created_at = arrow.get(version["createdAt"]).to("local") output_data["time"] = created_at.strftime("%Y%m%dT%H%M%SZ") output["data"] = output_data diff --git a/openpype/hosts/aftereffects/api/README.md b/openpype/hosts/aftereffects/api/README.md index 790c9f859a..9c4bad3689 100644 --- a/openpype/hosts/aftereffects/api/README.md +++ b/openpype/hosts/aftereffects/api/README.md @@ -1,6 +1,6 @@ # AfterEffects Integration -Requirements: This extension requires use of Javascript engine, which is +Requirements: This extension requires use of Javascript engine, which is available since CC 16.0. Please check your File>Project Settings>Expressions>Expressions Engine @@ -13,26 +13,28 @@ The After Effects integration requires two components to work; `extension` and ` To install the extension download [Extension Manager Command Line tool (ExManCmd)](https://github.com/Adobe-CEP/Getting-Started-guides/tree/master/Package%20Distribute%20Install#option-2---exmancmd). ``` -ExManCmd /install {path to avalon-core}\avalon\photoshop\extension.zxp +ExManCmd /install {path to addon}/api/extension.zxp ``` OR download [Anastasiy’s Extension Manager](https://install.anastasiy.com/) +`{path to addon}` will be most likely in your AppData (on Windows, in your user data folder in Linux and MacOS.) + ### Server The easiest way to get the server and After Effects launch is with: ``` -python -c ^"import avalon.photoshop;avalon.aftereffects.launch(""c:\Program Files\Adobe\Adobe After Effects 2020\Support Files\AfterFX.exe"")^" +python -c ^"import openpype.hosts.photoshop;openpype.hosts..aftereffects.launch(""c:\Program Files\Adobe\Adobe After Effects 2020\Support Files\AfterFX.exe"")^" ``` `avalon.aftereffects.launch` launches the application and server, and also closes the server when After Effects exists. ## Usage -The After Effects extension can be found under `Window > Extensions > OpenPype`. Once launched you should be presented with a panel like this: +The After Effects extension can be found under `Window > Extensions > AYON`. Once launched you should be presented with a panel like this: -![Avalon Panel](panel.PNG "Avalon Panel") +![Ayon Panel](panel.png "Ayon Panel") ## Developing @@ -43,8 +45,8 @@ When developing the extension you can load it [unsigned](https://github.com/Adob When signing the extension you can use this [guide](https://github.com/Adobe-CEP/Getting-Started-guides/tree/master/Package%20Distribute%20Install#package-distribute-install-guide). ``` -ZXPSignCmd -selfSignedCert NA NA Avalon Avalon-After-Effects avalon extension.p12 -ZXPSignCmd -sign {path to avalon-core}\avalon\aftereffects\extension {path to avalon-core}\avalon\aftereffects\extension.zxp extension.p12 avalon +ZXPSignCmd -selfSignedCert NA NA Ayon Avalon-After-Effects Ayon extension.p12 +ZXPSignCmd -sign {path to addon}/api/extension {path to addon}/api/extension.zxp extension.p12 Ayon ``` ### Plugin Examples @@ -52,14 +54,14 @@ ZXPSignCmd -sign {path to avalon-core}\avalon\aftereffects\extension {path to av These plugins were made with the [polly config](https://github.com/mindbender-studio/config). To fully integrate and load, you will have to use this config and add `image` to the [integration plugin](https://github.com/mindbender-studio/config/blob/master/polly/plugins/publish/integrate_asset.py). Expected deployed extension location on default Windows: -`c:\Program Files (x86)\Common Files\Adobe\CEP\extensions\com.openpype.AE.panel` +`c:\Program Files (x86)\Common Files\Adobe\CEP\extensions\io.ynput.AE.panel` For easier debugging of Javascript: https://community.adobe.com/t5/download-install/adobe-extension-debuger-problem/td-p/10911704?page=1 Add (optional) --enable-blink-features=ShadowDOMV0,CustomElementsV0 when starting Chrome then localhost:8092 -Or use Visual Studio Code https://medium.com/adobetech/extendscript-debugger-for-visual-studio-code-public-release-a2ff6161fa01 +Or use Visual Studio Code https://medium.com/adobetech/extendscript-debugger-for-visual-studio-code-public-release-a2ff6161fa01 ## Resources - https://javascript-tools-guide.readthedocs.io/introduction/index.html - https://github.com/Adobe-CEP/Getting-Started-guides diff --git a/openpype/hosts/aftereffects/api/extension.zxp b/openpype/hosts/aftereffects/api/extension.zxp index 933dc7dc6c..104a5c9e99 100644 Binary files a/openpype/hosts/aftereffects/api/extension.zxp and b/openpype/hosts/aftereffects/api/extension.zxp differ diff --git a/openpype/hosts/aftereffects/api/extension/.debug b/openpype/hosts/aftereffects/api/extension/.debug index b06ec515dd..20a6713ab2 100644 --- a/openpype/hosts/aftereffects/api/extension/.debug +++ b/openpype/hosts/aftereffects/api/extension/.debug @@ -1,32 +1,31 @@ - + - + - + - + - + - + - + - + - + - + - \ No newline at end of file diff --git a/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml b/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml index 7329a9e723..cf6ba67f44 100644 --- a/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml +++ b/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml @@ -1,8 +1,8 @@ - + - + @@ -38,7 +38,7 @@ - + ./index.html @@ -49,7 +49,7 @@ Panel - OpenPype + AYON 200 @@ -66,7 +66,7 @@ - ./icons/iconNormal.png + ./icons/ayon_logo.png ./icons/iconRollover.png ./icons/iconDisabled.png ./icons/iconDarkNormal.png diff --git a/openpype/hosts/aftereffects/api/extension/icons/ayon_logo.png b/openpype/hosts/aftereffects/api/extension/icons/ayon_logo.png new file mode 100644 index 0000000000..3a96f8e2b4 Binary files /dev/null and b/openpype/hosts/aftereffects/api/extension/icons/ayon_logo.png differ diff --git a/openpype/hosts/aftereffects/api/panel.png b/openpype/hosts/aftereffects/api/panel.png new file mode 100644 index 0000000000..d05ed35428 Binary files /dev/null and b/openpype/hosts/aftereffects/api/panel.png differ diff --git a/openpype/hosts/aftereffects/api/panel_failure.PNG b/openpype/hosts/aftereffects/api/panel_failure.PNG deleted file mode 100644 index 67afc4e212..0000000000 Binary files a/openpype/hosts/aftereffects/api/panel_failure.PNG and /dev/null differ diff --git a/openpype/hosts/aftereffects/api/panel_failure.png b/openpype/hosts/aftereffects/api/panel_failure.png new file mode 100644 index 0000000000..6e52a77d22 Binary files /dev/null and b/openpype/hosts/aftereffects/api/panel_failure.png differ diff --git a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py index bdb48e11f8..b44e986d83 100644 --- a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py @@ -60,8 +60,9 @@ class ExtractLocalRender(publish.Extractor): first_repre = not representations if instance.data["review"] and first_repre: repre_data["tags"] = ["review"] - thumbnail_path = os.path.join(staging_dir, files[0]) - instance.data["thumbnailSource"] = thumbnail_path + # TODO return back when Extract from source same as regular + # thumbnail_path = os.path.join(staging_dir, files[0]) + # instance.data["thumbnailSource"] = thumbnail_path representations.append(repre_data) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index ecf36abdd2..5870828b41 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -14,7 +14,7 @@ from openpype.pipeline import ( legacy_io, Creator as NewCreator, CreatedInstance, - Anatomy + Anatomy, ) @@ -27,28 +27,21 @@ class CreateSaver(NewCreator): description = "Fusion Saver to generate image sequence" icon = "fa5.eye" - instance_attributes = [ - "reviewable" - ] + instance_attributes = ["reviewable"] + image_format = "exr" # TODO: This should be renamed together with Nuke so it is aligned temp_rendering_path_template = ( - "{workdir}/renders/fusion/{subset}/{subset}.{frame}.{ext}") + "{workdir}/renders/fusion/{subset}/{subset}.{frame}.{ext}" + ) def create(self, subset_name, instance_data, pre_create_data): - self.pass_pre_attributes_to_instance( - instance_data, - pre_create_data + self.pass_pre_attributes_to_instance(instance_data, pre_create_data) + + instance_data.update( + {"id": "pyblish.avalon.instance", "subset": subset_name} ) - instance_data.update({ - "id": "pyblish.avalon.instance", - "subset": subset_name - }) - - # TODO: Add pre_create attributes to choose file format? - file_format = "OpenEXRFormat" - comp = get_current_comp() with comp_lock_and_undo_chunk(comp): args = (-32768, -32768) # Magical position numbers @@ -56,19 +49,6 @@ class CreateSaver(NewCreator): self._update_tool_with_data(saver, data=instance_data) - saver["OutputFormat"] = file_format - - # Check file format settings are available - if saver[file_format] is None: - raise RuntimeError( - f"File format is not set to {file_format}, this is a bug" - ) - - # Set file format attributes - saver[file_format]["Depth"] = 0 # Auto | float16 | float32 - # TODO Is this needed? - saver[file_format]["SaveAlpha"] = 1 - # Register the CreatedInstance instance = CreatedInstance( family=self.family, @@ -140,8 +120,15 @@ class CreateSaver(NewCreator): return original_subset = tool.GetData("openpype.subset") + original_format = tool.GetData( + "openpype.creator_attributes.image_format" + ) + subset = data["subset"] - if original_subset != subset: + if ( + original_subset != subset + or original_format != data["creator_attributes"]["image_format"] + ): self._configure_saver_tool(data, tool, subset) def _configure_saver_tool(self, data, tool, subset): @@ -151,17 +138,17 @@ class CreateSaver(NewCreator): anatomy = Anatomy() frame_padding = anatomy.templates["frame_padding"] + # get output format + ext = data["creator_attributes"]["image_format"] + # Subset change detected workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"]) - formatting_data.update({ - "workdir": workdir, - "frame": "0" * frame_padding, - "ext": "exr" - }) + formatting_data.update( + {"workdir": workdir, "frame": "0" * frame_padding, "ext": ext} + ) # build file path to render - filepath = self.temp_rendering_path_template.format( - **formatting_data) + filepath = self.temp_rendering_path_template.format(**formatting_data) comp = get_current_comp() tool["Clip"] = comp.ReverseMapPath(os.path.normpath(filepath)) @@ -201,7 +188,8 @@ class CreateSaver(NewCreator): attr_defs = [ self._get_render_target_enum(), self._get_reviewable_bool(), - self._get_frame_range_enum() + self._get_frame_range_enum(), + self._get_image_format_enum(), ] return attr_defs @@ -209,11 +197,7 @@ class CreateSaver(NewCreator): """Settings for publish page""" return self.get_pre_create_attr_defs() - def pass_pre_attributes_to_instance( - self, - instance_data, - pre_create_data - ): + def pass_pre_attributes_to_instance(self, instance_data, pre_create_data): creator_attrs = instance_data["creator_attributes"] = {} for pass_key in pre_create_data.keys(): creator_attrs[pass_key] = pre_create_data[pass_key] @@ -236,13 +220,13 @@ class CreateSaver(NewCreator): frame_range_options = { "asset_db": "Current asset context", "render_range": "From render in/out", - "comp_range": "From composition timeline" + "comp_range": "From composition timeline", } return EnumDef( "frame_range_source", items=frame_range_options, - label="Frame range source" + label="Frame range source", ) def _get_reviewable_bool(self): @@ -252,20 +236,33 @@ class CreateSaver(NewCreator): label="Review", ) + def _get_image_format_enum(self): + image_format_options = ["exr", "tga", "tif", "png", "jpg"] + return EnumDef( + "image_format", + items=image_format_options, + default=self.image_format, + label="Output Image Format", + ) + def apply_settings(self, project_settings): """Method called on initialization of plugin to apply settings.""" # plugin settings - plugin_settings = ( - project_settings["fusion"]["create"][self.__class__.__name__] - ) + plugin_settings = project_settings["fusion"]["create"][ + self.__class__.__name__ + ] # individual attributes self.instance_attributes = plugin_settings.get( - "instance_attributes") or self.instance_attributes - self.default_variants = plugin_settings.get( - "default_variants") or self.default_variants - self.temp_rendering_path_template = ( - plugin_settings.get("temp_rendering_path_template") - or self.temp_rendering_path_template + "instance_attributes", self.instance_attributes + ) + self.default_variants = plugin_settings.get( + "default_variants", self.default_variants + ) + self.temp_rendering_path_template = plugin_settings.get( + "temp_rendering_path_template", self.temp_rendering_path_template + ) + self.image_format = plugin_settings.get( + "image_format", self.image_format ) diff --git a/openpype/hosts/fusion/plugins/publish/extract_render_local.py b/openpype/hosts/fusion/plugins/publish/extract_render_local.py index 08d608139d..068df22c06 100644 --- a/openpype/hosts/fusion/plugins/publish/extract_render_local.py +++ b/openpype/hosts/fusion/plugins/publish/extract_render_local.py @@ -146,11 +146,15 @@ class FusionRenderLocal( staging_dir = os.path.dirname(path) + files = [os.path.basename(f) for f in expected_files] + if len(expected_files) == 1: + files = files[0] + repre = { "name": ext[1:], "ext": ext[1:], "frameStart": f"%0{padding}d" % start, - "files": [os.path.basename(f) for f in expected_files], + "files": files, "stagingDir": staging_dir, } diff --git a/openpype/hosts/houdini/plugins/publish/collect_chunk_size.py b/openpype/hosts/houdini/plugins/publish/collect_chunk_size.py index 1c867e930a..f8cd4089e2 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_chunk_size.py +++ b/openpype/hosts/houdini/plugins/publish/collect_chunk_size.py @@ -14,18 +14,13 @@ class CollectChunkSize(pyblish.api.InstancePlugin, hosts = ["houdini"] targets = ["local", "remote"] label = "Collect Chunk Size" - chunkSize = 999999 + chunk_size = 999999 def process(self, instance): # need to get the chunk size info from the setting attr_values = self.get_attr_values_from_data(instance.data) instance.data["chunkSize"] = attr_values.get("chunkSize") - @classmethod - def apply_settings(cls, project_settings): - project_setting = project_settings["houdini"]["publish"]["CollectChunkSize"] # noqa - cls.chunkSize = project_setting["chunk_size"] - @classmethod def get_attribute_defs(cls): return [ @@ -33,7 +28,6 @@ class CollectChunkSize(pyblish.api.InstancePlugin, minimum=1, maximum=999999, decimals=0, - default=cls.chunkSize, + default=cls.chunk_size, label="Frame Per Task") - ] diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 298084a4e8..8531233bb2 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -511,3 +511,20 @@ def render_resolution(width, height): finally: rt.renderWidth = current_renderWidth rt.renderHeight = current_renderHeight + + +@contextlib.contextmanager +def suspended_refresh(): + """Suspended refresh for scene and modify panel redraw. + """ + if is_headless(): + yield + return + rt.disableSceneRedraw() + rt.suspendEditing() + try: + yield + + finally: + rt.enableSceneRedraw() + rt.resumeEditing() diff --git a/openpype/hosts/max/api/preview_animation.py b/openpype/hosts/max/api/preview_animation.py index 6c7b8eaa80..74579b165f 100644 --- a/openpype/hosts/max/api/preview_animation.py +++ b/openpype/hosts/max/api/preview_animation.py @@ -198,8 +198,8 @@ def _render_preview_animation_max_pre_2024( res_width, res_height, filename=filepath ) dib = rt.gw.getViewportDib() - dib_width = rt.renderWidth - dib_height = rt.renderHeight + dib_width = float(dib.width) + dib_height = float(dib.height) # aspect ratio viewportRatio = dib_width / dib_height renderRatio = float(res_width / res_height) diff --git a/openpype/hosts/max/plugins/publish/extract_pointcache.py b/openpype/hosts/max/plugins/publish/extract_alembic.py similarity index 56% rename from openpype/hosts/max/plugins/publish/extract_pointcache.py rename to openpype/hosts/max/plugins/publish/extract_alembic.py index f6a8500c08..24e0121a61 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/max/plugins/publish/extract_alembic.py @@ -39,45 +39,41 @@ Note: """ import os import pyblish.api -from openpype.pipeline import publish +from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt from openpype.hosts.max.api import maintained_selection +from openpype.hosts.max.api.lib import suspended_refresh +from openpype.lib import BoolDef -class ExtractAlembic(publish.Extractor): +class ExtractAlembic(publish.Extractor, + OptionalPyblishPluginMixin): order = pyblish.api.ExtractorOrder label = "Extract Pointcache" hosts = ["max"] families = ["pointcache"] + optional = True def process(self, instance): - start = instance.data["frameStartHandle"] - end = instance.data["frameEndHandle"] - - self.log.debug("Extracting pointcache ...") + if not self.is_active(instance.data): + return parent_dir = self.staging_dir(instance) file_name = "{name}.abc".format(**instance.data) path = os.path.join(parent_dir, file_name) - # We run the render - self.log.info("Writing alembic '%s' to '%s'" % (file_name, parent_dir)) - - rt.AlembicExport.ArchiveType = rt.name("ogawa") - rt.AlembicExport.CoordinateSystem = rt.name("maya") - rt.AlembicExport.StartFrame = start - rt.AlembicExport.EndFrame = end - - with maintained_selection(): - # select and export - node_list = instance.data["members"] - rt.Select(node_list) - rt.exportFile( - path, - rt.name("noPrompt"), - selectedOnly=True, - using=rt.AlembicExport, - ) + with suspended_refresh(): + self._set_abc_attributes(instance) + with maintained_selection(): + # select and export + node_list = instance.data["members"] + rt.Select(node_list) + rt.exportFile( + path, + rt.name("noPrompt"), + selectedOnly=True, + using=rt.AlembicExport, + ) if "representations" not in instance.data: instance.data["representations"] = [] @@ -89,3 +85,51 @@ class ExtractAlembic(publish.Extractor): "stagingDir": parent_dir, } instance.data["representations"].append(representation) + + def _set_abc_attributes(self, instance): + start = instance.data["frameStartHandle"] + end = instance.data["frameEndHandle"] + attr_values = self.get_attr_values_from_data(instance.data) + custom_attrs = attr_values.get("custom_attrs", False) + if not custom_attrs: + self.log.debug( + "No Custom Attributes included in this abc export...") + rt.AlembicExport.ArchiveType = rt.Name("ogawa") + rt.AlembicExport.CoordinateSystem = rt.Name("maya") + rt.AlembicExport.StartFrame = start + rt.AlembicExport.EndFrame = end + rt.AlembicExport.CustomAttributes = custom_attrs + + @classmethod + def get_attribute_defs(cls): + return [ + BoolDef("custom_attrs", + label="Custom Attributes", + default=False), + ] + + +class ExtractCameraAlembic(ExtractAlembic): + """Extract Camera with AlembicExport.""" + + label = "Extract Alembic Camera" + families = ["camera"] + + +class ExtractModel(ExtractAlembic): + """Extract Geometry in Alembic Format""" + label = "Extract Geometry (Alembic)" + families = ["model"] + + def _set_abc_attributes(self, instance): + attr_values = self.get_attr_values_from_data(instance.data) + custom_attrs = attr_values.get("custom_attrs", False) + if not custom_attrs: + self.log.debug( + "No Custom Attributes included in this abc export...") + rt.AlembicExport.ArchiveType = rt.name("ogawa") + rt.AlembicExport.CoordinateSystem = rt.name("maya") + rt.AlembicExport.CustomAttributes = custom_attrs + rt.AlembicExport.UVs = True + rt.AlembicExport.VertexColors = True + rt.AlembicExport.PreserveInstances = True diff --git a/openpype/hosts/max/plugins/publish/extract_camera_abc.py b/openpype/hosts/max/plugins/publish/extract_camera_abc.py deleted file mode 100644 index a42f27be6e..0000000000 --- a/openpype/hosts/max/plugins/publish/extract_camera_abc.py +++ /dev/null @@ -1,64 +0,0 @@ -import os - -import pyblish.api -from pymxs import runtime as rt - -from openpype.hosts.max.api import maintained_selection -from openpype.pipeline import OptionalPyblishPluginMixin, publish - - -class ExtractCameraAlembic(publish.Extractor, OptionalPyblishPluginMixin): - """Extract Camera with AlembicExport.""" - - order = pyblish.api.ExtractorOrder - 0.1 - label = "Extract Alembic Camera" - hosts = ["max"] - families = ["camera"] - optional = True - - def process(self, instance): - if not self.is_active(instance.data): - return - start = instance.data["frameStartHandle"] - end = instance.data["frameEndHandle"] - - self.log.info("Extracting Camera ...") - - stagingdir = self.staging_dir(instance) - filename = "{name}.abc".format(**instance.data) - path = os.path.join(stagingdir, filename) - - # We run the render - self.log.info(f"Writing alembic '{filename}' to '{stagingdir}'") - - rt.AlembicExport.ArchiveType = rt.Name("ogawa") - rt.AlembicExport.CoordinateSystem = rt.Name("maya") - rt.AlembicExport.StartFrame = start - rt.AlembicExport.EndFrame = end - rt.AlembicExport.CustomAttributes = True - - with maintained_selection(): - # select and export - node_list = instance.data["members"] - rt.Select(node_list) - rt.ExportFile( - path, - rt.Name("noPrompt"), - selectedOnly=True, - using=rt.AlembicExport, - ) - - self.log.info("Performing Extraction ...") - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { - "name": "abc", - "ext": "abc", - "files": filename, - "stagingDir": stagingdir, - "frameStart": start, - "frameEnd": end, - } - instance.data["representations"].append(representation) - self.log.info(f"Extracted instance '{instance.name}' to: {path}") diff --git a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py index 537c88eb4d..4b5631b05f 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py @@ -20,13 +20,10 @@ class ExtractCameraFbx(publish.Extractor, OptionalPyblishPluginMixin): if not self.is_active(instance.data): return - self.log.debug("Extracting Camera ...") stagingdir = self.staging_dir(instance) filename = "{name}.fbx".format(**instance.data) filepath = os.path.join(stagingdir, filename) - self.log.info(f"Writing fbx file '{filename}' to '{filepath}'") - rt.FBXExporterSetParam("Animation", True) rt.FBXExporterSetParam("Cameras", True) rt.FBXExporterSetParam("AxisConversionMethod", "Animation") diff --git a/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py b/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py index a7a889c587..791cc65fcd 100644 --- a/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py +++ b/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py @@ -26,7 +26,6 @@ class ExtractMaxSceneRaw(publish.Extractor, OptionalPyblishPluginMixin): filename = "{name}.max".format(**instance.data) max_path = os.path.join(stagingdir, filename) - self.log.info("Writing max file '%s' to '%s'" % (filename, max_path)) if "representations" not in instance.data: instance.data["representations"] = [] diff --git a/openpype/hosts/max/plugins/publish/extract_model.py b/openpype/hosts/max/plugins/publish/extract_model.py deleted file mode 100644 index 38f4848c5e..0000000000 --- a/openpype/hosts/max/plugins/publish/extract_model.py +++ /dev/null @@ -1,63 +0,0 @@ -import os -import pyblish.api -from openpype.pipeline import publish, OptionalPyblishPluginMixin -from pymxs import runtime as rt -from openpype.hosts.max.api import maintained_selection - - -class ExtractModel(publish.Extractor, OptionalPyblishPluginMixin): - """ - Extract Geometry in Alembic Format - """ - - order = pyblish.api.ExtractorOrder - 0.1 - label = "Extract Geometry (Alembic)" - hosts = ["max"] - families = ["model"] - optional = True - - def process(self, instance): - if not self.is_active(instance.data): - return - - self.log.debug("Extracting Geometry ...") - - stagingdir = self.staging_dir(instance) - filename = "{name}.abc".format(**instance.data) - filepath = os.path.join(stagingdir, filename) - - # We run the render - self.log.info("Writing alembic '%s' to '%s'" % (filename, stagingdir)) - - rt.AlembicExport.ArchiveType = rt.name("ogawa") - rt.AlembicExport.CoordinateSystem = rt.name("maya") - rt.AlembicExport.CustomAttributes = True - rt.AlembicExport.UVs = True - rt.AlembicExport.VertexColors = True - rt.AlembicExport.PreserveInstances = True - - with maintained_selection(): - # select and export - node_list = instance.data["members"] - rt.Select(node_list) - rt.exportFile( - filepath, - rt.name("noPrompt"), - selectedOnly=True, - using=rt.AlembicExport, - ) - - self.log.info("Performing Extraction ...") - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { - "name": "abc", - "ext": "abc", - "files": filename, - "stagingDir": stagingdir, - } - instance.data["representations"].append(representation) - self.log.info( - "Extracted instance '%s' to: %s" % (instance.name, filepath) - ) diff --git a/openpype/hosts/max/plugins/publish/extract_model_fbx.py b/openpype/hosts/max/plugins/publish/extract_model_fbx.py index fd48ed5007..6c42fd5364 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_model_fbx.py @@ -20,12 +20,9 @@ class ExtractModelFbx(publish.Extractor, OptionalPyblishPluginMixin): if not self.is_active(instance.data): return - self.log.debug("Extracting Geometry ...") - stagingdir = self.staging_dir(instance) filename = "{name}.fbx".format(**instance.data) filepath = os.path.join(stagingdir, filename) - self.log.info("Writing FBX '%s' to '%s'" % (filepath, stagingdir)) rt.FBXExporterSetParam("Animation", False) rt.FBXExporterSetParam("Cameras", False) @@ -46,7 +43,6 @@ class ExtractModelFbx(publish.Extractor, OptionalPyblishPluginMixin): using=rt.FBXEXP, ) - self.log.info("Performing Extraction ...") if "representations" not in instance.data: instance.data["representations"] = [] diff --git a/openpype/hosts/max/plugins/publish/extract_model_obj.py b/openpype/hosts/max/plugins/publish/extract_model_obj.py index a5d9ad6597..8464353164 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_obj.py +++ b/openpype/hosts/max/plugins/publish/extract_model_obj.py @@ -3,6 +3,7 @@ import pyblish.api from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt from openpype.hosts.max.api import maintained_selection +from openpype.hosts.max.api.lib import suspended_refresh from openpype.pipeline.publish import KnownPublishError @@ -21,25 +22,21 @@ class ExtractModelObj(publish.Extractor, OptionalPyblishPluginMixin): if not self.is_active(instance.data): return - self.log.debug("Extracting Geometry ...") - stagingdir = self.staging_dir(instance) filename = "{name}.obj".format(**instance.data) filepath = os.path.join(stagingdir, filename) - self.log.info("Writing OBJ '%s' to '%s'" % (filepath, stagingdir)) - - self.log.info("Performing Extraction ...") - with maintained_selection(): - # select and export - node_list = instance.data["members"] - rt.Select(node_list) - rt.exportFile( - filepath, - rt.name("noPrompt"), - selectedOnly=True, - using=rt.ObjExp, - ) + with suspended_refresh(): + with maintained_selection(): + # select and export + node_list = instance.data["members"] + rt.Select(node_list) + rt.exportFile( + filepath, + rt.name("noPrompt"), + selectedOnly=True, + using=rt.ObjExp, + ) if not os.path.exists(filepath): raise KnownPublishError( "File {} wasn't produced by 3ds max, please check the logs.") diff --git a/openpype/hosts/max/plugins/publish/validate_resolution_setting.py b/openpype/hosts/max/plugins/publish/validate_resolution_setting.py index 7d91a7b991..1c4b05c556 100644 --- a/openpype/hosts/max/plugins/publish/validate_resolution_setting.py +++ b/openpype/hosts/max/plugins/publish/validate_resolution_setting.py @@ -1,9 +1,12 @@ import pyblish.api +from pymxs import runtime as rt from openpype.pipeline import ( - PublishValidationError, OptionalPyblishPluginMixin ) -from pymxs import runtime as rt +from openpype.pipeline.publish import ( + RepairAction, + PublishValidationError +) from openpype.hosts.max.api.lib import reset_scene_resolution @@ -16,6 +19,7 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, hosts = ["max"] label = "Validate Resolution Setting" optional = True + actions = [RepairAction] def process(self, instance): if not self.is_active(instance.data): diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index 7bc17ff504..12562a6b6f 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -260,7 +260,7 @@ def _install_menu(): "Create...", lambda: host_tools.show_publisher( parent=( - main_window if nuke.NUKE_VERSION_RELEASE >= 14 else None + main_window if nuke.NUKE_VERSION_MAJOR >= 14 else None ), tab="create" ) @@ -271,7 +271,7 @@ def _install_menu(): "Publish...", lambda: host_tools.show_publisher( parent=( - main_window if nuke.NUKE_VERSION_RELEASE >= 14 else None + main_window if nuke.NUKE_VERSION_MAJOR >= 14 else None ), tab="publish" ) diff --git a/openpype/hosts/photoshop/api/README.md b/openpype/hosts/photoshop/api/README.md index 7bd2bcb1bf..a207d21f2f 100644 --- a/openpype/hosts/photoshop/api/README.md +++ b/openpype/hosts/photoshop/api/README.md @@ -9,7 +9,7 @@ The Photoshop integration requires two components to work; `extension` and `serv To install the extension download [Extension Manager Command Line tool (ExManCmd)](https://github.com/Adobe-CEP/Getting-Started-guides/tree/master/Package%20Distribute%20Install#option-2---exmancmd). ``` -ExManCmd /install {path to avalon-core}\avalon\photoshop\extension.zxp +ExManCmd /install {path to addon}/api/extension.zxp ``` ### Server @@ -17,16 +17,16 @@ ExManCmd /install {path to avalon-core}\avalon\photoshop\extension.zxp The easiest way to get the server and Photoshop launch is with: ``` -python -c ^"import avalon.photoshop;avalon.photoshop.launch(""C:\Program Files\Adobe\Adobe Photoshop 2020\Photoshop.exe"")^" +python -c ^"import openpype.hosts.photoshop;openpype.hosts.photoshop.launch(""C:\Program Files\Adobe\Adobe Photoshop 2020\Photoshop.exe"")^" ``` `avalon.photoshop.launch` launches the application and server, and also closes the server when Photoshop exists. ## Usage -The Photoshop extension can be found under `Window > Extensions > Avalon`. Once launched you should be presented with a panel like this: +The Photoshop extension can be found under `Window > Extensions > Ayon`. Once launched you should be presented with a panel like this: -![Avalon Panel](panel.PNG "Avalon Panel") +![Ayon Panel](panel.png "AYON Panel") ## Developing @@ -37,7 +37,7 @@ When developing the extension you can load it [unsigned](https://github.com/Adob When signing the extension you can use this [guide](https://github.com/Adobe-CEP/Getting-Started-guides/tree/master/Package%20Distribute%20Install#package-distribute-install-guide). ``` -ZXPSignCmd -selfSignedCert NA NA Avalon Avalon-Photoshop avalon extension.p12 +ZXPSignCmd -selfSignedCert NA NA Ayon Ayon-Photoshop Ayon extension.p12 ZXPSignCmd -sign {path to avalon-core}\avalon\photoshop\extension {path to avalon-core}\avalon\photoshop\extension.zxp extension.p12 avalon ``` diff --git a/openpype/hosts/photoshop/api/extension.zxp b/openpype/hosts/photoshop/api/extension.zxp index 39b766cd0d..26a73a37fd 100644 Binary files a/openpype/hosts/photoshop/api/extension.zxp and b/openpype/hosts/photoshop/api/extension.zxp differ diff --git a/openpype/hosts/photoshop/api/extension/.debug b/openpype/hosts/photoshop/api/extension/.debug index a0e2f3c9e0..4cea03cb41 100644 --- a/openpype/hosts/photoshop/api/extension/.debug +++ b/openpype/hosts/photoshop/api/extension/.debug @@ -1,9 +1,9 @@ - + - \ No newline at end of file + diff --git a/openpype/hosts/photoshop/api/extension/CSXS/manifest.xml b/openpype/hosts/photoshop/api/extension/CSXS/manifest.xml index 2089d06da1..16d85be9b4 100644 --- a/openpype/hosts/photoshop/api/extension/CSXS/manifest.xml +++ b/openpype/hosts/photoshop/api/extension/CSXS/manifest.xml @@ -1,7 +1,7 @@ - + - + @@ -16,7 +16,7 @@ - + ./index.html @@ -32,7 +32,7 @@ Panel - OpenPype + AYON 300 @@ -44,7 +44,7 @@ - ./icons/avalon-logo-48.png + ./icons/ayon_logo.png diff --git a/openpype/hosts/photoshop/api/extension/icons/avalon-logo-48.png b/openpype/hosts/photoshop/api/extension/icons/avalon-logo-48.png deleted file mode 100644 index 33fe2a606b..0000000000 Binary files a/openpype/hosts/photoshop/api/extension/icons/avalon-logo-48.png and /dev/null differ diff --git a/openpype/hosts/photoshop/api/extension/icons/ayon_logo.png b/openpype/hosts/photoshop/api/extension/icons/ayon_logo.png new file mode 100644 index 0000000000..3a96f8e2b4 Binary files /dev/null and b/openpype/hosts/photoshop/api/extension/icons/ayon_logo.png differ diff --git a/openpype/hosts/photoshop/api/panel.PNG b/openpype/hosts/photoshop/api/panel.PNG deleted file mode 100644 index be5db3b8df..0000000000 Binary files a/openpype/hosts/photoshop/api/panel.PNG and /dev/null differ diff --git a/openpype/hosts/aftereffects/api/panel.PNG b/openpype/hosts/photoshop/api/panel.png similarity index 100% rename from openpype/hosts/aftereffects/api/panel.PNG rename to openpype/hosts/photoshop/api/panel.png diff --git a/openpype/hosts/photoshop/api/panel_failure.PNG b/openpype/hosts/photoshop/api/panel_failure.PNG deleted file mode 100644 index 67afc4e212..0000000000 Binary files a/openpype/hosts/photoshop/api/panel_failure.PNG and /dev/null differ diff --git a/openpype/hosts/photoshop/api/panel_failure.png b/openpype/hosts/photoshop/api/panel_failure.png new file mode 100644 index 0000000000..6e52a77d22 Binary files /dev/null and b/openpype/hosts/photoshop/api/panel_failure.png differ diff --git a/openpype/hosts/photoshop/plugins/publish/extract_review.py b/openpype/hosts/photoshop/plugins/publish/extract_review.py index d5dac417d7..09c5d63aa5 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_review.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_review.py @@ -170,8 +170,7 @@ class ExtractReview(publish.Extractor): # Generate mov. mov_path = os.path.join(staging_dir, "review.mov") self.log.info(f"Generate mov review: {mov_path}") - args = [ - ffmpeg_path, + args = ffmpeg_path + [ "-y", "-i", source_files_pattern, "-vf", "pad=ceil(iw/2)*2:ceil(ih/2)*2", @@ -224,6 +223,7 @@ class ExtractReview(publish.Extractor): "stagingDir": staging_dir, "tags": ["thumbnail", "delete"] }) + instance.data["thumbnailPath"] = thumbnail_path def _check_and_resize(self, processed_img_names, source_files_pattern, staging_dir): diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py index 9109bf6726..eb06875601 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py @@ -257,8 +257,6 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): if 'shot' not in instance.data.get('family', ''): continue - name = instance.data["asset"] - # get handles handle_start = int(instance.data["handleStart"]) handle_end = int(instance.data["handleEnd"]) @@ -286,6 +284,8 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): parents = instance.data.get('parents', []) self.log.debug(f"parents: {pformat(parents)}") + # Split by '/' for AYON where asset is a path + name = instance.data["asset"].split("/")[-1] actual = {name: in_info} for parent in reversed(parents): diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_explicit_colorspace.py b/openpype/hosts/traypublisher/plugins/publish/collect_explicit_colorspace.py index 5db2b0cbad..75c26ac958 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_explicit_colorspace.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_explicit_colorspace.py @@ -5,6 +5,7 @@ from openpype.pipeline import ( ) from openpype.lib import EnumDef from openpype.pipeline import colorspace +from openpype.pipeline.publish import KnownPublishError class CollectColorspace(pyblish.api.InstancePlugin, @@ -26,18 +27,44 @@ class CollectColorspace(pyblish.api.InstancePlugin, def process(self, instance): values = self.get_attr_values_from_data(instance.data) - colorspace = values.get("colorspace", None) - if colorspace is None: + colorspace_value = values.get("colorspace", None) + if colorspace_value is None: return - self.log.debug("Explicit colorspace set to: {}".format(colorspace)) + color_data = colorspace.convert_colorspace_enumerator_item( + colorspace_value, self.config_items) + + colorspace_name = self._colorspace_name_by_type(color_data) + self.log.debug("Explicit colorspace name: {}".format(colorspace_name)) context = instance.context for repre in instance.data.get("representations", {}): self.set_representation_colorspace( representation=repre, context=context, - colorspace=colorspace + colorspace=colorspace_name + ) + + def _colorspace_name_by_type(self, colorspace_data): + """ + Returns colorspace name by type + + Arguments: + colorspace_data (dict): colorspace data + + Returns: + str: colorspace name + """ + if colorspace_data["type"] == "colorspaces": + return colorspace_data["name"] + elif colorspace_data["type"] == "roles": + return colorspace_data["colorspace"] + else: + raise KnownPublishError( + ( + "Collecting of colorspace failed. used config is missing " + "colorspace type: '{}' . Please contact your pipeline TD." + ).format(colorspace_data['type']) ) @classmethod diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py index e00ac64244..b99b634da1 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py @@ -155,8 +155,6 @@ class CollectShotInstance(pyblish.api.InstancePlugin): else {} ) - asset_name = instance.data["asset"] - # get handles handle_start = int(instance.data["handleStart"]) handle_end = int(instance.data["handleEnd"]) @@ -177,6 +175,8 @@ class CollectShotInstance(pyblish.api.InstancePlugin): parents = instance.data.get('parents', []) + # Split by '/' for AYON where asset is a path + asset_name = instance.data["asset"].split("/")[-1] actual = {asset_name: in_info} for parent in reversed(parents): diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_colorspace.py b/openpype/hosts/traypublisher/plugins/publish/validate_colorspace.py index 03f9f299b2..58c40938d2 100644 --- a/openpype/hosts/traypublisher/plugins/publish/validate_colorspace.py +++ b/openpype/hosts/traypublisher/plugins/publish/validate_colorspace.py @@ -33,7 +33,19 @@ class ValidateColorspace(pyblish.api.InstancePlugin, config_path = colorspace_data["config"]["path"] if config_path not in config_colorspaces: colorspaces = get_ocio_config_colorspaces(config_path) - config_colorspaces[config_path] = set(colorspaces) + if not colorspaces.get("colorspaces"): + message = ( + f"OCIO config '{config_path}' does not contain any " + "colorspaces. This is an error in the OCIO config. " + "Contact your pipeline TD.", + ) + raise PublishValidationError( + title="Colorspace validation", + message=message, + description=message + ) + config_colorspaces[config_path] = set( + colorspaces["colorspaces"]) colorspace = colorspace_data["colorspace"] self.log.debug( diff --git a/openpype/modules/ftrack/lib/custom_attributes.py b/openpype/modules/ftrack/lib/custom_attributes.py index 3e40bb02f2..76c7bcd403 100644 --- a/openpype/modules/ftrack/lib/custom_attributes.py +++ b/openpype/modules/ftrack/lib/custom_attributes.py @@ -66,7 +66,7 @@ def get_openpype_attr(session, split_hierarchical=True, query_keys=None): "select {}" " from CustomAttributeConfiguration" # Kept `pype` for Backwards Compatibility - " where group.name in (\"pype\", \"{}\")" + " where group.name in (\"pype\", \"ayon\", \"{}\")" ).format(", ".join(query_keys), CUST_ATTR_GROUP) all_avalon_attr = session.query(cust_attrs_query).all() for cust_attr in all_avalon_attr: diff --git a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index a1aa7c0daa..68a31035f6 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -21,7 +21,7 @@ def get_pype_attr(session, split_hierarchical=True): "select id, entity_type, object_type_id, is_hierarchical, default" " from CustomAttributeConfiguration" # Kept `pype` for Backwards Compatibility - " where group.name in (\"pype\", \"{}\")" + " where group.name in (\"pype\", \"ayon\", \"{}\")" ).format(CUST_ATTR_GROUP) all_avalon_attr = session.query(cust_attrs_query).all() for cust_attr in all_avalon_attr: diff --git a/openpype/plugins/publish/collect_hierarchy.py b/openpype/plugins/publish/collect_hierarchy.py index b5fd1e4bb9..32f10ba4c8 100644 --- a/openpype/plugins/publish/collect_hierarchy.py +++ b/openpype/plugins/publish/collect_hierarchy.py @@ -61,8 +61,9 @@ class CollectHierarchy(pyblish.api.ContextPlugin): "resolutionHeight": instance.data["resolutionHeight"], "pixelAspect": instance.data["pixelAspect"] } - - actual = {instance.data["asset"]: shot_data} + # Split by '/' for AYON where asset is a path + name = instance.data["asset"].split("/")[-1] + actual = {name: shot_data} for parent in reversed(instance.data["parents"]): next_dict = {} diff --git a/openpype/plugins/publish/collect_resources_path.py b/openpype/plugins/publish/collect_resources_path.py index 0f29fec054..c8b67a3d05 100644 --- a/openpype/plugins/publish/collect_resources_path.py +++ b/openpype/plugins/publish/collect_resources_path.py @@ -68,7 +68,6 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): ] def process(self, instance): - anatomy = instance.context.data["anatomy"] template_data = copy.deepcopy(instance.data["anatomyData"]) @@ -80,11 +79,18 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): "representation": "TEMP" }) - # For the first time publish - if instance.data.get("hierarchy"): - template_data.update({ - "hierarchy": instance.data["hierarchy"] - }) + # Add fill keys for editorial publishing creating new entity + # TODO handle in editorial plugin + if instance.data.get("newAssetPublishing"): + if "hierarchy" not in template_data: + template_data["hierarchy"] = instance.data["hierarchy"] + + if "asset" not in template_data: + asset_name = instance.data["asset"].split("/")[-1] + template_data["asset"] = asset_name + template_data["folder"] = { + "name": asset_name + } publish_templates = anatomy.templates_obj["publish"] if "folder" in publish_templates: diff --git a/openpype/plugins/publish/extract_hierarchy_to_ayon.py b/openpype/plugins/publish/extract_hierarchy_to_ayon.py index ef69369d67..b601a3fc29 100644 --- a/openpype/plugins/publish/extract_hierarchy_to_ayon.py +++ b/openpype/plugins/publish/extract_hierarchy_to_ayon.py @@ -204,7 +204,8 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin): project_item = None project_children_context = None - for key, value in context.data["hierarchyContext"].items(): + hierarchy_context = copy.deepcopy(context.data["hierarchyContext"]) + for key, value in hierarchy_context.items(): project_item = copy.deepcopy(value) project_children_context = project_item.pop("childs", None) project_item["name"] = key @@ -223,23 +224,24 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin): valid_ids = set() hierarchy_queue = collections.deque() - hierarchy_queue.append((project_id, project_children_context)) + hierarchy_queue.append((project_id, "", project_children_context)) while hierarchy_queue: queue_item = hierarchy_queue.popleft() - parent_id, children_context = queue_item + parent_id, parent_path, children_context = queue_item if not children_context: continue - for asset, asset_info in children_context.items(): + for folder_name, folder_info in children_context.items(): + folder_path = "{}/{}".format(parent_path, folder_name) if ( - asset not in active_folder_paths - and not asset_info.get("childs") + folder_path not in active_folder_paths + and not folder_info.get("childs") ): continue - asset_name = asset.split("/")[-1] + item_id = uuid.uuid4().hex - new_item = copy.deepcopy(asset_info) - new_item["name"] = asset_name + new_item = copy.deepcopy(folder_info) + new_item["name"] = folder_name new_item["children"] = [] new_children_context = new_item.pop("childs", None) tasks = new_item.pop("tasks", {}) @@ -253,9 +255,11 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin): items_by_id[item_id] = new_item parent_id_by_item_id[item_id] = parent_id - if asset in active_folder_paths: + if folder_path in active_folder_paths: valid_ids.add(item_id) - hierarchy_queue.append((item_id, new_children_context)) + hierarchy_queue.append( + (item_id, folder_path, new_children_context) + ) if not valid_ids: return None diff --git a/openpype/plugins/publish/integrate_thumbnail_ayon.py b/openpype/plugins/publish/integrate_thumbnail_ayon.py index 1947c9dd4c..e56c567667 100644 --- a/openpype/plugins/publish/integrate_thumbnail_ayon.py +++ b/openpype/plugins/publish/integrate_thumbnail_ayon.py @@ -5,7 +5,21 @@ pull into a scene. This one is used only as image describing content of published item and - shows up only in Loader in right column section. + shows up only in Loader or WebUI. + + Instance must have 'published_representations' to + be able to integrate thumbnail. + Possible sources of thumbnail paths: + - instance.data["thumbnailPath"] + - representation with 'thumbnail' name in 'published_representations' + - context.data["thumbnailPath"] + + Notes: + Issue with 'thumbnail' representation is that we most likely don't + want to integrate it as representation. Integrated representation + is polluting Loader and database without real usage. That's why + they usually have 'delete' tag to skip the integration. + """ import os @@ -92,11 +106,8 @@ class IntegrateThumbnailsAYON(pyblish.api.ContextPlugin): continue # Find thumbnail path on instance - thumbnail_source = instance.data.get("thumbnailSource") - thumbnail_path = instance.data.get("thumbnailPath") thumbnail_path = ( - thumbnail_source - or thumbnail_path + instance.data.get("thumbnailPath") or self._get_instance_thumbnail_path(published_repres) ) if thumbnail_path: diff --git a/openpype/settings/defaults/project_settings/fusion.json b/openpype/settings/defaults/project_settings/fusion.json index ab24727db5..0edcae060a 100644 --- a/openpype/settings/defaults/project_settings/fusion.json +++ b/openpype/settings/defaults/project_settings/fusion.json @@ -25,7 +25,8 @@ "instance_attributes": [ "reviewable", "farm_rendering" - ] + ], + "image_format": "exr" } }, "publish": { diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 0001b23967..813e7153ea 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -62,6 +62,12 @@ "Main" ] }, + "CreateMantraIFD": { + "enabled": true, + "default_variants": [ + "Main" + ] + }, "CreateMantraROP": { "enabled": true, "default_variants": [ @@ -137,14 +143,14 @@ } }, "publish": { + "CollectAssetHandles": { + "use_asset_handles": true + }, "CollectChunkSize": { "enabled": true, "optional": true, "chunk_size": 999999 }, - "CollectAssetHandles": { - "use_asset_handles": true - }, "ValidateContainers": { "enabled": true, "optional": true, diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index fa2f694747..1778530311 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -436,7 +436,7 @@ "viewTransform": "sRGB gamma" } }, - "mel_workspace": "workspace -fr \"shaders\" \"renderData/shaders\";\nworkspace -fr \"images\" \"renders/maya\";\nworkspace -fr \"particles\" \"particles\";\nworkspace -fr \"mayaAscii\" \"\";\nworkspace -fr \"mayaBinary\" \"\";\nworkspace -fr \"scene\" \"\";\nworkspace -fr \"alembicCache\" \"cache/alembic\";\nworkspace -fr \"renderData\" \"renderData\";\nworkspace -fr \"sourceImages\" \"sourceimages\";\nworkspace -fr \"fileCache\" \"cache/nCache\";\n", + "mel_workspace": "workspace -fr \"shaders\" \"renderData/shaders\";\nworkspace -fr \"images\" \"renders/maya\";\nworkspace -fr \"particles\" \"particles\";\nworkspace -fr \"mayaAscii\" \"\";\nworkspace -fr \"mayaBinary\" \"\";\nworkspace -fr \"scene\" \"\";\nworkspace -fr \"alembicCache\" \"cache/alembic\";\nworkspace -fr \"renderData\" \"renderData\";\nworkspace -fr \"sourceImages\" \"sourceimages\";\nworkspace -fr \"fileCache\" \"cache/nCache\";\nworkspace -fr \"autoSave\" \"autosave\";", "ext_mapping": { "model": "ma", "mayaAscii": "ma", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json index 342411f8a5..5177d8bc7c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -80,6 +80,19 @@ "farm_rendering": "Farm rendering" } ] + }, + { + "key": "image_format", + "label": "Output Image Format", + "type": "enum", + "multiselect": false, + "enum_items": [ + {"exr": "exr"}, + {"tga": "tga"}, + {"png": "png"}, + {"tif": "tif"}, + {"jpg": "jpg"} + ] } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json index f37738c4ec..213ec9d04e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_create.json @@ -69,6 +69,10 @@ "key": "CreateKarmaROP", "label": "Create Karma ROP" }, + { + "key": "CreateMantraIFD", + "label": "Create Mantra IFD" + }, { "key": "CreateMantraROP", "label": "Create Mantra ROP" diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json index 935c2a4c35..aaaf4311b7 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_publish.json @@ -25,6 +25,31 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "CollectChunkSize", + "label": "Collect Chunk Size", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "number", + "key": "chunk_size", + "label": "Frames Per Task" + } + ] + }, { "type": "label", "label": "Validators" @@ -55,31 +80,6 @@ } ] }, - { - "type": "dict", - "collapsible": true, - "checkbox_key": "enabled", - "key": "CollectChunkSize", - "label": "Collect Chunk Size", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "optional", - "label": "Optional" - }, - { - "type": "number", - "key": "chunk_size", - "label": "Frames Per Task" - } - ] - }, { "type": "dict", "collapsible": true, diff --git a/openpype/style/style.css b/openpype/style/style.css index ca368f84f8..f6ecebd683 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -512,52 +512,58 @@ QAbstractItemView::item:selected:hover { } /* Row colors (alternate colors) are from left - right */ -QAbstractItemView:branch { - background: transparent; +QTreeView::branch { + background: {color:bg-view}; +} +QTreeView::branch:hover { + background: {color:bg-view}; +} +QTreeView::branch:selected { + background: {color:bg-view}; } QAbstractItemView::branch:open:has-children:!has-siblings, QAbstractItemView::branch:open:has-children:has-siblings { border-image: none; image: url(:/openpype/images/branch_open.png); - background: transparent; + background: {color:bg-view}; } QAbstractItemView::branch:open:has-children:!has-siblings:hover, QAbstractItemView::branch:open:has-children:has-siblings:hover { border-image: none; image: url(:/openpype/images/branch_open_on.png); - background: transparent; + background: {color:bg-view}; } QAbstractItemView::branch:has-children:!has-siblings:closed, QAbstractItemView::branch:closed:has-children:has-siblings { border-image: none; image: url(:/openpype/images/branch_closed.png); - background: transparent; + background: {color:bg-view}; } QAbstractItemView::branch:has-children:!has-siblings:closed:hover, QAbstractItemView::branch:closed:has-children:has-siblings:hover { border-image: none; image: url(:/openpype/images/branch_closed_on.png); - background: transparent; + background: {color:bg-view}; } QAbstractItemView::branch:has-siblings:!adjoins-item { border-image: none; image: url(:/openpype/images/transparent.png); - background: transparent; + background: {color:bg-view}; } QAbstractItemView::branch:has-siblings:adjoins-item { border-image: none; image: url(:/openpype/images/transparent.png); - background: transparent; + background: {color:bg-view}; } QAbstractItemView::branch:!has-children:!has-siblings:adjoins-item { border-image: none; image: url(:/openpype/images/transparent.png); - background: transparent; + background: {color:bg-view}; } CompleterView { diff --git a/openpype/tools/ayon_loader/models/products.py b/openpype/tools/ayon_loader/models/products.py index daa36aefdc..135f28df97 100644 --- a/openpype/tools/ayon_loader/models/products.py +++ b/openpype/tools/ayon_loader/models/products.py @@ -44,7 +44,7 @@ def version_item_from_entity(version): # NOTE There is also 'updatedAt', should be used that instead? # TODO skip conversion - converting to '%Y%m%dT%H%M%SZ' is because # 'PrettyTimeDelegate' expects it - created_at = arrow.get(version["createdAt"]) + created_at = arrow.get(version["createdAt"]).to("local") published_time = created_at.strftime("%Y%m%dT%H%M%SZ") author = version["author"] version_num = version["version"] diff --git a/openpype/tools/ayon_workfiles/models/workfiles.py b/openpype/tools/ayon_workfiles/models/workfiles.py index 907b9b5383..d74a8e164d 100644 --- a/openpype/tools/ayon_workfiles/models/workfiles.py +++ b/openpype/tools/ayon_workfiles/models/workfiles.py @@ -606,7 +606,7 @@ class PublishWorkfilesModel: print("Failed to format workfile path: {}".format(exc)) dirpath, filename = os.path.split(workfile_path) - created_at = arrow.get(repre_entity["createdAt"]) + created_at = arrow.get(repre_entity["createdAt"].to("local")) return FileItem( dirpath, filename, diff --git a/openpype/vendor/python/common/ayon_api/__init__.py b/openpype/vendor/python/common/ayon_api/__init__.py index dc3d361f46..cc15ad9170 100644 --- a/openpype/vendor/python/common/ayon_api/__init__.py +++ b/openpype/vendor/python/common/ayon_api/__init__.py @@ -75,8 +75,6 @@ from ._api import ( download_installer, upload_installer, - get_dependencies_info, - update_dependency_info, get_dependency_packages, create_dependency_package, update_dependency_package, @@ -277,8 +275,6 @@ __all__ = ( "download_installer", "upload_installer", - "get_dependencies_info", - "update_dependency_info", "get_dependency_packages", "create_dependency_package", "update_dependency_package", diff --git a/openpype/vendor/python/common/ayon_api/_api.py b/openpype/vendor/python/common/ayon_api/_api.py index 9d4fc697ae..a0374a08b9 100644 --- a/openpype/vendor/python/common/ayon_api/_api.py +++ b/openpype/vendor/python/common/ayon_api/_api.py @@ -611,16 +611,6 @@ def upload_installer(*args, **kwargs): # Dependency packages -def get_dependencies_info(*args, **kwargs): - con = get_server_api_connection() - return con.get_dependencies_info(*args, **kwargs) - - -def update_dependency_info(*args, **kwargs): - con = get_server_api_connection() - return con.update_dependency_info(*args, **kwargs) - - def download_dependency_package(*args, **kwargs): con = get_server_api_connection() return con.download_dependency_package(*args, **kwargs) diff --git a/openpype/vendor/python/common/ayon_api/entity_hub.py b/openpype/vendor/python/common/ayon_api/entity_hub.py index 61d740fe57..f894c428a8 100644 --- a/openpype/vendor/python/common/ayon_api/entity_hub.py +++ b/openpype/vendor/python/common/ayon_api/entity_hub.py @@ -7,9 +7,21 @@ import six from ._api import get_server_api_connection from .utils import create_entity_id, convert_entity_id, slugify_string -UNKNOWN_VALUE = object() -PROJECT_PARENT_ID = object() -_NOT_SET = object() + +class _CustomNone(object): + def __init__(self, name=None): + self._name = name or "CustomNone" + + def __repr__(self): + return "<{}>".format(self._name) + + def __bool__(self): + return False + + +UNKNOWN_VALUE = _CustomNone("UNKNOWN_VALUE") +PROJECT_PARENT_ID = _CustomNone("PROJECT_PARENT_ID") +_NOT_SET = _CustomNone("_NOT_SET") class EntityHub(object): @@ -1284,7 +1296,10 @@ class BaseEntity(object): changes["name"] = self._name if self._entity_hub.allow_data_changes: - if self._orig_data != self._data: + if ( + self._data is not UNKNOWN_VALUE + and self._orig_data != self._data + ): changes["data"] = self._data if self._orig_thumbnail_id != self._thumbnail_id: diff --git a/openpype/vendor/python/common/ayon_api/server_api.py b/openpype/vendor/python/common/ayon_api/server_api.py index e4e7146279..4aed4e811a 100644 --- a/openpype/vendor/python/common/ayon_api/server_api.py +++ b/openpype/vendor/python/common/ayon_api/server_api.py @@ -8,6 +8,7 @@ import collections import platform import copy import uuid +import warnings from contextlib import contextmanager import six @@ -1022,17 +1023,10 @@ class ServerAPI(object): for attr, filter_value in filters.items(): query.set_variable_value(attr, filter_value) - # Backwards compatibility for server 0.3.x - # - will be removed in future releases - major, minor, _, _, _ = self.server_version_tuple - access_groups_field = "accessGroups" - if major == 0 and minor <= 3: - access_groups_field = "roles" - for parsed_data in query.continuous_query(self): for user in parsed_data["users"]: - user[access_groups_field] = json.loads( - user[access_groups_field]) + user["accessGroups"] = json.loads( + user["accessGroups"]) yield user def get_user(self, username=None): @@ -2044,14 +2038,6 @@ class ServerAPI(object): elif entity_type == "user": entity_type_defaults = set(DEFAULT_USER_FIELDS) - # Backwards compatibility for server 0.3.x - # - will be removed in future releases - major, minor, _, _, _ = self.server_version_tuple - if major == 0 and minor <= 3: - entity_type_defaults.discard("accessGroups") - entity_type_defaults.discard("defaultAccessGroups") - entity_type_defaults.add("roles") - entity_type_defaults.add("defaultRoles") else: raise ValueError("Unknown entity type \"{}\"".format(entity_type)) @@ -2306,125 +2292,8 @@ class ServerAPI(object): progress=progress ) - def get_dependencies_info(self): - """Information about dependency packages on server. - - Example data structure: - { - "packages": [ - { - "name": str, - "platform": str, - "checksum": str, - "sources": list[dict[str, Any]], - "supportedAddons": dict[str, str], - "pythonModules": dict[str, str] - } - ], - "productionPackage": str - } - - Deprecated: - Deprecated since server version 0.2.1. Use - 'get_dependency_packages' instead. - - Returns: - dict[str, Any]: Information about dependency packages known for - server. - """ - - major, minor, patch, _, _ = self.server_version_tuple - if major == 0 and (minor < 2 or (minor == 2 and patch < 1)): - result = self.get("dependencies") - return result.data - packages = self.get_dependency_packages() - packages["productionPackage"] = None - return packages - - def update_dependency_info( - self, - name, - platform_name, - size, - checksum, - checksum_algorithm=None, - supported_addons=None, - python_modules=None, - sources=None - ): - """Update or create dependency package for identifiers. - - The endpoint can be used to create or update dependency package. - - - Deprecated: - Deprecated for server version 0.2.1. Use - 'create_dependency_pacakge' instead. - - Args: - name (str): Name of dependency package. - platform_name (Literal["windows", "linux", "darwin"]): Platform - for which is dependency package targeted. - size (int): Size of dependency package in bytes. - checksum (str): Checksum of archive file where dependencies are. - checksum_algorithm (Optional[str]): Algorithm used to calculate - checksum. By default, is used 'md5' (defined by server). - supported_addons (Optional[dict[str, str]]): Name of addons for - which was the package created. - '{"": "", ...}' - python_modules (Optional[dict[str, str]]): Python modules in - dependencies package. - '{"": "", ...}' - sources (Optional[list[dict[str, Any]]]): Information about - sources where dependency package is available. - """ - - kwargs = { - key: value - for key, value in ( - ("checksumAlgorithm", checksum_algorithm), - ("supportedAddons", supported_addons), - ("pythonModules", python_modules), - ("sources", sources), - ) - if value - } - - response = self.put( - "dependencies", - name=name, - platform=platform_name, - size=size, - checksum=checksum, - **kwargs - ) - response.raise_for_status("Failed to create/update dependency") - return response.data - - def _get_dependency_package_route( - self, filename=None, platform_name=None - ): - major, minor, patch, _, _ = self.server_version_tuple - if (major, minor, patch) <= (0, 2, 0): - # Backwards compatibility for AYON server 0.2.0 and lower - self.log.warning(( - "Using deprecated dependency package route." - " Please update your AYON server to version 0.2.1 or higher." - " Backwards compatibility for this route will be removed" - " in future releases of ayon-python-api." - )) - if platform_name is None: - platform_name = platform.system().lower() - base = "dependencies" - if not filename: - return base - return "{}/{}/{}".format(base, filename, platform_name) - - if (major, minor) <= (0, 3): - endpoint = "desktop/dependency_packages" - else: - endpoint = "desktop/dependencyPackages" - + def _get_dependency_package_route(self, filename=None): + endpoint = "desktop/dependencyPackages" if filename: return "{}/{}".format(endpoint, filename) return endpoint @@ -2535,14 +2404,21 @@ class ServerAPI(object): """Remove dependency package for specific platform. Args: - filename (str): Filename of dependency package. Or name of package - for server version 0.2.0 or lower. - platform_name (Optional[str]): Which platform of the package - should be removed. Current platform is used if not passed. - Deprecated since version 0.2.1 + filename (str): Filename of dependency package. + platform_name (Optional[str]): Deprecated. """ - route = self._get_dependency_package_route(filename, platform_name) + if platform_name is not None: + warnings.warn( + ( + "Argument 'platform_name' is deprecated in" + " 'delete_dependency_package'. The argument will be" + " removed, please modify your code accordingly." + ), + DeprecationWarning + ) + + route = self._get_dependency_package_route(filename) response = self.delete(route) response.raise_for_status("Failed to delete dependency file") return response.data @@ -2567,18 +2443,25 @@ class ServerAPI(object): to download. dst_directory (str): Where the file should be downloaded. dst_filename (str): Name of destination filename. - platform_name (Optional[str]): Name of platform for which the - dependency package is targeted. Default value is - current platform. Deprecated since server version 0.2.1. + platform_name (Optional[str]): Deprecated. chunk_size (Optional[int]): Download chunk size. progress (Optional[TransferProgress]): Object that gives ability to track download progress. Returns: str: Filepath to downloaded file. - """ + """ - route = self._get_dependency_package_route(src_filename, platform_name) + if platform_name is not None: + warnings.warn( + ( + "Argument 'platform_name' is deprecated in" + " 'download_dependency_package'. The argument will be" + " removed, please modify your code accordingly." + ), + DeprecationWarning + ) + route = self._get_dependency_package_route(src_filename) package_filepath = os.path.join(dst_directory, dst_filename) self.download_file( route, @@ -2597,32 +2480,24 @@ class ServerAPI(object): src_filepath (str): Path to a package file. dst_filename (str): Dependency package filename or name of package for server version 0.2.0 or lower. Must be unique. - platform_name (Optional[str]): For which platform is the - package targeted. Deprecated since server version 0.2.1. + platform_name (Optional[str]): Deprecated. progress (Optional[TransferProgress]): Object to keep track about upload state. """ - route = self._get_dependency_package_route(dst_filename, platform_name) + if platform_name is not None: + warnings.warn( + ( + "Argument 'platform_name' is deprecated in" + " 'upload_dependency_package'. The argument will be" + " removed, please modify your code accordingly." + ), + DeprecationWarning + ) + + route = self._get_dependency_package_route(dst_filename) self.upload_file(route, src_filepath, progress=progress) - def create_dependency_package_basename(self, platform_name=None): - """Create basename for dependency package file. - - Deprecated: - Use 'create_dependency_package_basename' from `ayon_api` or - `ayon_api.utils` instead. - - Args: - platform_name (Optional[str]): Name of platform for which the - bundle is targeted. Default value is current platform. - - Returns: - str: Dependency package name with timestamp and platform. - """ - - return create_dependency_package_basename(platform_name) - def upload_addon_zip(self, src_filepath, progress=None): """Upload addon zip file to server. @@ -2650,14 +2525,6 @@ class ServerAPI(object): ) return response.json() - def _get_bundles_route(self): - major, minor, patch, _, _ = self.server_version_tuple - # Backwards compatibility for AYON server 0.3.0 - # - first version where bundles were available - if major == 0 and minor == 3 and patch == 0: - return "desktop/bundles" - return "bundles" - def get_bundles(self): """Server bundles with basic information. @@ -2688,7 +2555,7 @@ class ServerAPI(object): dict[str, Any]: Server bundles with basic information. """ - response = self.get(self._get_bundles_route()) + response = self.get("bundles") response.raise_for_status() return response.data @@ -2731,7 +2598,7 @@ class ServerAPI(object): if value is not None: body[key] = value - response = self.post(self._get_bundles_route(), **body) + response = self.post("bundles", **body) response.raise_for_status() def update_bundle( @@ -2766,7 +2633,7 @@ class ServerAPI(object): if value is not None } response = self.patch( - "{}/{}".format(self._get_bundles_route(), bundle_name), + "{}/{}".format("bundles", bundle_name), **body ) response.raise_for_status() @@ -2779,7 +2646,7 @@ class ServerAPI(object): """ response = self.delete( - "{}/{}".format(self._get_bundles_route(), bundle_name) + "{}/{}".format("bundles", bundle_name) ) response.raise_for_status() @@ -3102,16 +2969,13 @@ class ServerAPI(object): - test how it behaves if there is not any production/staging bundle. - Warnings: - For AYON server < 0.3.0 bundle name will be ignored. - Example output: { "addons": [ { "name": "addon-name", "version": "addon-version", - "settings": {...} + "settings": {...}, "siteSettings": {...} } ] @@ -3121,7 +2985,6 @@ class ServerAPI(object): dict[str, Any]: All settings for single bundle. """ - major, minor, _, _, _ = self.server_version_tuple query_values = { key: value for key, value in ( @@ -3137,21 +3000,8 @@ class ServerAPI(object): if site_id: query_values["site_id"] = site_id - if major == 0 and minor >= 3: - url = "settings" - else: - # Backward compatibility for AYON server < 0.3.0 - url = "settings/addons" - query_values.pop("bundle_name", None) - for new_key, old_key in ( - ("project_name", "project"), - ("site_id", "site"), - ): - if new_key in query_values: - query_values[old_key] = query_values.pop(new_key) - query = prepare_query_string(query_values) - response = self.get("{}{}".format(url, query)) + response = self.get("settings{}".format(query)) response.raise_for_status() return response.data @@ -3194,15 +3044,10 @@ class ServerAPI(object): use_site=use_site ) if only_values: - major, minor, patch, _, _ = self.server_version_tuple - if major == 0 and minor >= 3: - output = { - addon["name"]: addon["settings"] - for addon in output["addons"] - } - else: - # Backward compatibility for AYON server < 0.3.0 - output = output["settings"] + output = { + addon["name"]: addon["settings"] + for addon in output["addons"] + } return output def get_addons_project_settings( @@ -3263,15 +3108,10 @@ class ServerAPI(object): use_site=use_site ) if only_values: - major, minor, patch, _, _ = self.server_version_tuple - if major == 0 and minor >= 3: - output = { - addon["name"]: addon["settings"] - for addon in output["addons"] - } - else: - # Backward compatibility for AYON server < 0.3.0 - output = output["settings"] + output = { + addon["name"]: addon["settings"] + for addon in output["addons"] + } return output def get_addons_settings( diff --git a/openpype/vendor/python/common/ayon_api/version.py b/openpype/vendor/python/common/ayon_api/version.py index bc1107da1e..ce0173a248 100644 --- a/openpype/vendor/python/common/ayon_api/version.py +++ b/openpype/vendor/python/common/ayon_api/version.py @@ -1,2 +1,2 @@ """Package declaring Python API for Ayon server.""" -__version__ = "1.0.0-rc.1" +__version__ = "1.0.0-rc.3" diff --git a/openpype/version.py b/openpype/version.py index cdaafa0559..c4ff4dde95 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.17.7-nightly.7" +__version__ = "3.18.2-nightly.2" diff --git a/pyproject.toml b/pyproject.toml index 21ba7d1199..e64018498f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.17.6" # OpenPype +version = "3.18.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" diff --git a/server_addon/fusion/server/settings.py b/server_addon/fusion/server/settings.py index 92fb362c66..1bc12773d2 100644 --- a/server_addon/fusion/server/settings.py +++ b/server_addon/fusion/server/settings.py @@ -25,6 +25,16 @@ def _create_saver_instance_attributes_enum(): ] +def _image_format_enum(): + return [ + {"value": "exr", "label": "exr"}, + {"value": "tga", "label": "tga"}, + {"value": "png", "label": "png"}, + {"value": "tif", "label": "tif"}, + {"value": "jpg", "label": "jpg"}, + ] + + class CreateSaverPluginModel(BaseSettingsModel): _isGroup = True temp_rendering_path_template: str = Field( @@ -39,6 +49,10 @@ class CreateSaverPluginModel(BaseSettingsModel): enum_resolver=_create_saver_instance_attributes_enum, title="Instance attributes" ) + image_format: str = Field( + enum_resolver=_image_format_enum, + title="Output Image Format" + ) class CreatPluginsModel(BaseSettingsModel): @@ -89,7 +103,8 @@ DEFAULT_VALUES = { "instance_attributes": [ "reviewable", "farm_rendering" - ] + ], + "image_format": "exr" } } } diff --git a/server_addon/fusion/server/version.py b/server_addon/fusion/server/version.py index 3dc1f76bc6..485f44ac21 100644 --- a/server_addon/fusion/server/version.py +++ b/server_addon/fusion/server/version.py @@ -1 +1 @@ -__version__ = "0.1.0" +__version__ = "0.1.1" diff --git a/server_addon/houdini/server/settings/create.py b/server_addon/houdini/server/settings/create.py index e8db917849..a5ca4d477b 100644 --- a/server_addon/houdini/server/settings/create.py +++ b/server_addon/houdini/server/settings/create.py @@ -52,6 +52,9 @@ class CreatePluginsModel(BaseSettingsModel): CreateKarmaROP: CreatorModel = Field( default_factory=CreatorModel, title="Create Karma ROP") + CreateMantraIFD: CreatorModel = Field( + default_factory=CreatorModel, + title="Create Mantra IFD") CreateMantraROP: CreatorModel = Field( default_factory=CreatorModel, title="Create Mantra ROP") @@ -114,6 +117,10 @@ DEFAULT_HOUDINI_CREATE_SETTINGS = { "enabled": True, "default_variants": ["Main"] }, + "CreateMantraIFD": { + "enabled": True, + "default_variants": ["Main"] + }, "CreateMantraROP": { "enabled": True, "default_variants": ["Main"] diff --git a/server_addon/houdini/server/settings/publish.py b/server_addon/houdini/server/settings/publish.py index 92a676b0d0..f551b3a209 100644 --- a/server_addon/houdini/server/settings/publish.py +++ b/server_addon/houdini/server/settings/publish.py @@ -13,6 +13,14 @@ class CollectAssetHandlesModel(BaseSettingsModel): title="Use asset handles") +class CollectChunkSizeModel(BaseSettingsModel): + """Collect Chunk Size.""" + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + chunk_size: int = Field( + title="Frames Per Task") + + class ValidateWorkfilePathsModel(BaseSettingsModel): enabled: bool = Field(title="Enabled") optional: bool = Field(title="Optional") @@ -38,6 +46,10 @@ class PublishPluginsModel(BaseSettingsModel): title="Collect Asset Handles.", section="Collectors" ) + CollectChunkSize: CollectChunkSizeModel = Field( + default_factory=CollectChunkSizeModel, + title="Collect Chunk Size." + ) ValidateContainers: BasicValidateModel = Field( default_factory=BasicValidateModel, title="Validate Latest Containers.", @@ -63,6 +75,11 @@ DEFAULT_HOUDINI_PUBLISH_SETTINGS = { "CollectAssetHandles": { "use_asset_handles": True }, + "CollectChunkSize": { + "enabled": True, + "optional": True, + "chunk_size": 999999 + }, "ValidateContainers": { "enabled": True, "optional": True, diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py index 75cf7831c4..6232f7ab18 100644 --- a/server_addon/houdini/server/version.py +++ b/server_addon/houdini/server/version.py @@ -1 +1 @@ -__version__ = "0.2.9" +__version__ = "0.2.10" diff --git a/server_addon/maya/server/settings/main.py b/server_addon/maya/server/settings/main.py index 62fd12ec8a..a5b573a75e 100644 --- a/server_addon/maya/server/settings/main.py +++ b/server_addon/maya/server/settings/main.py @@ -97,7 +97,7 @@ DEFAULT_MEL_WORKSPACE_SETTINGS = "\n".join(( 'workspace -fr "renderData" "renderData";', 'workspace -fr "sourceImages" "sourceimages";', 'workspace -fr "fileCache" "cache/nCache";', - 'workspace -fr "autoSave" "autosave"', + 'workspace -fr "autoSave" "autosave";', '', )) diff --git a/server_addon/photoshop/server/settings/publish_plugins.py b/server_addon/photoshop/server/settings/publish_plugins.py index 2863979ca9..21e7d670f0 100644 --- a/server_addon/photoshop/server/settings/publish_plugins.py +++ b/server_addon/photoshop/server/settings/publish_plugins.py @@ -29,7 +29,7 @@ class ColorCodeMappings(BaseSettingsModel): ) layer_name_regex: list[str] = Field( - "", + default_factory=list, title="Layer name regex" ) diff --git a/server_addon/photoshop/server/version.py b/server_addon/photoshop/server/version.py index d4b9e2d7f3..a242f0e757 100644 --- a/server_addon/photoshop/server/version.py +++ b/server_addon/photoshop/server/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring addon version.""" -__version__ = "0.1.0" +__version__ = "0.1.1" diff --git a/start.py b/start.py index 3b020c76c0..54a252ccf3 100644 --- a/start.py +++ b/start.py @@ -366,8 +366,8 @@ def run_disk_mapping_commands(settings): destination = destination.replace("/", "\\").rstrip("\\") source = source.replace("/", "\\").rstrip("\\") # Add slash after ':' ('G:' -> 'G:\') - if destination.endswith(":"): - destination += "\\" + if source.endswith(":"): + source += "\\" else: destination = destination.rstrip("/") source = source.rstrip("/") diff --git a/tests/integration/hosts/aftereffects/test_deadline_publish_in_aftereffects.py b/tests/integration/hosts/aftereffects/test_deadline_publish_in_aftereffects.py index 30761693a8..1aa612207d 100644 --- a/tests/integration/hosts/aftereffects/test_deadline_publish_in_aftereffects.py +++ b/tests/integration/hosts/aftereffects/test_deadline_publish_in_aftereffects.py @@ -60,7 +60,7 @@ class TestDeadlinePublishInAfterEffects(AEDeadlinePublishTestClass): name="renderTest_taskMain")) failures.append( - DBAssert.count_of_types(dbcon, "representation", 4)) + DBAssert.count_of_types(dbcon, "representation", 3)) additional_args = {"context.subset": "workfileTest_task", "context.ext": "aep"} @@ -77,7 +77,7 @@ class TestDeadlinePublishInAfterEffects(AEDeadlinePublishTestClass): additional_args = {"context.subset": "renderTest_taskMain", "name": "thumbnail"} failures.append( - DBAssert.count_of_types(dbcon, "representation", 1, + DBAssert.count_of_types(dbcon, "representation", 0, additional_args=additional_args)) additional_args = {"context.subset": "renderTest_taskMain", diff --git a/tests/integration/hosts/aftereffects/test_deadline_publish_in_aftereffects_multicomposition.py b/tests/integration/hosts/aftereffects/test_deadline_publish_in_aftereffects_multicomposition.py index 0e9cd3b00d..4254b951b5 100644 --- a/tests/integration/hosts/aftereffects/test_deadline_publish_in_aftereffects_multicomposition.py +++ b/tests/integration/hosts/aftereffects/test_deadline_publish_in_aftereffects_multicomposition.py @@ -71,7 +71,7 @@ class TestDeadlinePublishInAfterEffectsMultiComposition(AEDeadlinePublishTestCla name="renderTest_taskMain2")) failures.append( - DBAssert.count_of_types(dbcon, "representation", 5)) + DBAssert.count_of_types(dbcon, "representation", 4)) additional_args = {"context.subset": "workfileTest_task", "context.ext": "aep"} @@ -89,7 +89,7 @@ class TestDeadlinePublishInAfterEffectsMultiComposition(AEDeadlinePublishTestCla additional_args = {"context.subset": "renderTest_taskMain", "name": "thumbnail"} failures.append( - DBAssert.count_of_types(dbcon, "representation", 1, + DBAssert.count_of_types(dbcon, "representation", 0, additional_args=additional_args)) additional_args = {"context.subset": "renderTest_taskMain", diff --git a/tests/integration/hosts/aftereffects/test_publish_in_aftereffects.py b/tests/integration/hosts/aftereffects/test_publish_in_aftereffects.py index 2e4f343a5a..f13043d8bb 100644 --- a/tests/integration/hosts/aftereffects/test_publish_in_aftereffects.py +++ b/tests/integration/hosts/aftereffects/test_publish_in_aftereffects.py @@ -58,7 +58,7 @@ class TestPublishInAfterEffects(AELocalPublishTestClass): name="renderTest_taskMain")) failures.append( - DBAssert.count_of_types(dbcon, "representation", 4)) + DBAssert.count_of_types(dbcon, "representation", 3)) additional_args = {"context.subset": "workfileTest_task", "context.ext": "aep"} @@ -75,7 +75,7 @@ class TestPublishInAfterEffects(AELocalPublishTestClass): additional_args = {"context.subset": "renderTest_taskMain", "name": "thumbnail"} failures.append( - DBAssert.count_of_types(dbcon, "representation", 1, + DBAssert.count_of_types(dbcon, "representation", 0, additional_args=additional_args)) additional_args = {"context.subset": "renderTest_taskMain", diff --git a/tests/integration/hosts/aftereffects/test_publish_in_aftereffects_legacy.py b/tests/integration/hosts/aftereffects/test_publish_in_aftereffects_legacy.py index a62036e4a7..b99db24e75 100644 --- a/tests/integration/hosts/aftereffects/test_publish_in_aftereffects_legacy.py +++ b/tests/integration/hosts/aftereffects/test_publish_in_aftereffects_legacy.py @@ -60,7 +60,7 @@ class TestPublishInAfterEffects(AELocalPublishTestClass): name="renderTest_taskMain")) failures.append( - DBAssert.count_of_types(dbcon, "representation", 4)) + DBAssert.count_of_types(dbcon, "representation", 2)) additional_args = {"context.subset": "workfileTest_task", "context.ext": "aep"} @@ -77,7 +77,7 @@ class TestPublishInAfterEffects(AELocalPublishTestClass): additional_args = {"context.subset": "renderTest_taskMain", "name": "thumbnail"} failures.append( - DBAssert.count_of_types(dbcon, "representation", 1, + DBAssert.count_of_types(dbcon, "representation", 0, additional_args=additional_args)) additional_args = {"context.subset": "renderTest_taskMain", @@ -89,7 +89,7 @@ class TestPublishInAfterEffects(AELocalPublishTestClass): additional_args = {"context.subset": "renderTest_taskMain", "name": "thumbnail"} failures.append( - DBAssert.count_of_types(dbcon, "representation", 1, + DBAssert.count_of_types(dbcon, "representation", 0, additional_args=additional_args)) additional_args = {"context.subset": "renderTest_taskMain", diff --git a/tests/integration/hosts/aftereffects/test_publish_in_aftereffects_multiframe.py b/tests/integration/hosts/aftereffects/test_publish_in_aftereffects_multiframe.py index dcf34844d1..bd9d3e9b50 100644 --- a/tests/integration/hosts/aftereffects/test_publish_in_aftereffects_multiframe.py +++ b/tests/integration/hosts/aftereffects/test_publish_in_aftereffects_multiframe.py @@ -45,7 +45,7 @@ class TestPublishInAfterEffects(AELocalPublishTestClass): name="renderTest_taskMain")) failures.append( - DBAssert.count_of_types(dbcon, "representation", 4)) + DBAssert.count_of_types(dbcon, "representation", 3)) additional_args = {"context.subset": "workfileTest_task", "context.ext": "aep"} @@ -62,7 +62,7 @@ class TestPublishInAfterEffects(AELocalPublishTestClass): additional_args = {"context.subset": "renderTest_taskMain", "name": "thumbnail"} failures.append( - DBAssert.count_of_types(dbcon, "representation", 1, + DBAssert.count_of_types(dbcon, "representation", 0, additional_args=additional_args)) additional_args = {"context.subset": "renderTest_taskMain", diff --git a/tests/integration/hosts/maya/test_deadline_publish_in_maya.py b/tests/integration/hosts/maya/test_deadline_publish_in_maya.py index 7d2b409db3..79d6c22a26 100644 --- a/tests/integration/hosts/maya/test_deadline_publish_in_maya.py +++ b/tests/integration/hosts/maya/test_deadline_publish_in_maya.py @@ -54,7 +54,7 @@ class TestDeadlinePublishInMaya(MayaDeadlinePublishTestClass): DBAssert.count_of_types(dbcon, "subset", 1, name="workfileTest_task")) - failures.append(DBAssert.count_of_types(dbcon, "representation", 8)) + failures.append(DBAssert.count_of_types(dbcon, "representation", 7)) # hero included additional_args = {"context.subset": "modelMain", @@ -85,7 +85,7 @@ class TestDeadlinePublishInMaya(MayaDeadlinePublishTestClass): additional_args = {"context.subset": "renderTest_taskMain_beauty", "context.ext": "jpg"} failures.append( - DBAssert.count_of_types(dbcon, "representation", 1, + DBAssert.count_of_types(dbcon, "representation", 0, additional_args=additional_args)) additional_args = {"context.subset": "renderTest_taskMain_beauty", diff --git a/tests/integration/hosts/maya/test_deadline_publish_in_maya/expected/test_project/test_asset/publish/render/renderTest_taskMain_beauty/v001/test_project_test_asset_renderTest_taskMain_beauty_v001.jpg b/tests/integration/hosts/maya/test_deadline_publish_in_maya/expected/test_project/test_asset/publish/render/renderTest_taskMain_beauty/v001/test_project_test_asset_renderTest_taskMain_beauty_v001.jpg deleted file mode 100644 index 79ff799e8b..0000000000 Binary files a/tests/integration/hosts/maya/test_deadline_publish_in_maya/expected/test_project/test_asset/publish/render/renderTest_taskMain_beauty/v001/test_project_test_asset_renderTest_taskMain_beauty_v001.jpg and /dev/null differ diff --git a/tests/integration/hosts/nuke/test_deadline_publish_in_nuke.py b/tests/integration/hosts/nuke/test_deadline_publish_in_nuke.py index a4026f195b..586078ead6 100644 --- a/tests/integration/hosts/nuke/test_deadline_publish_in_nuke.py +++ b/tests/integration/hosts/nuke/test_deadline_publish_in_nuke.py @@ -69,7 +69,7 @@ class TestDeadlinePublishInNuke(NukeDeadlinePublishTestClass): name="workfileTest_task")) failures.append( - DBAssert.count_of_types(dbcon, "representation", 4)) + DBAssert.count_of_types(dbcon, "representation", 3)) additional_args = {"context.subset": "workfileTest_task", "context.ext": "nk"} @@ -86,7 +86,7 @@ class TestDeadlinePublishInNuke(NukeDeadlinePublishTestClass): additional_args = {"context.subset": "renderTest_taskMain", "name": "thumbnail"} failures.append( - DBAssert.count_of_types(dbcon, "representation", 1, + DBAssert.count_of_types(dbcon, "representation", 0, additional_args=additional_args)) additional_args = {"context.subset": "renderTest_taskMain",