diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 5c264e4d98..35564c2bf0 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -35,6 +35,15 @@ body:
label: Version
description: What version are you running? Look to OpenPype Tray
options:
+ - 3.16.7-nightly.1
+ - 3.16.6
+ - 3.16.6-nightly.1
+ - 3.16.5
+ - 3.16.5-nightly.5
+ - 3.16.5-nightly.4
+ - 3.16.5-nightly.3
+ - 3.16.5-nightly.2
+ - 3.16.5-nightly.1
- 3.16.4
- 3.16.4-nightly.3
- 3.16.4-nightly.2
@@ -126,15 +135,6 @@ body:
- 3.14.9
- 3.14.9-nightly.5
- 3.14.9-nightly.4
- - 3.14.9-nightly.3
- - 3.14.9-nightly.2
- - 3.14.9-nightly.1
- - 3.14.8
- - 3.14.8-nightly.4
- - 3.14.8-nightly.3
- - 3.14.8-nightly.2
- - 3.14.8-nightly.1
- - 3.14.7
validations:
required: true
- type: dropdown
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f1948b1a3f..0d7620869b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,916 @@
# Changelog
+## [3.16.6](https://github.com/ynput/OpenPype/tree/3.16.6)
+
+
+[Full Changelog](https://github.com/ynput/OpenPype/compare/3.16.5...3.16.6)
+
+### **🆕 New features**
+
+
+
+Workfiles tool: Refactor workfiles tool (for AYON) #5550
+
+Refactored workfiles tool to new tool. Separated backend and frontend logic. Refactored logic is AYON-centric and is used only in AYON mode, so it does not affect OpenPype.
+
+
+___
+
+
+
+
+
+AfterEffects: added validator for missing files in FootageItems #5590
+
+Published composition in AE could contain multiple FootageItems as a layers. If FootageItem contains imported file and it doesn't exist, render triggered by Publish process will silently fail and no output is generated. This could cause failure later in the process with unclear reason. (In `ExtractReview`).This PR adds validation to protect from this.
+
+
+___
+
+
+
+### **🚀 Enhancements**
+
+
+
+Maya: Yeti Cache Include viewport preview settings from source #5561
+
+When publishing and loading yeti caches persist the display output and preview colors + settings to ensure consistency in the view
+
+
+___
+
+
+
+
+
+Houdini: validate colorspace in review rop #5322
+
+Adding a validator that checks if 'OCIO Colorspace' parameter on review rop was set to a valid value.It is a step towards managing colorspace in review ropvalid values are the ones in the dropdown menuthis validator also provides some helper actions This PR is related to #4836 and #4833
+
+
+___
+
+
+
+
+
+Colorspace: adding abstraction of publishing related functions #5497
+
+The functionality of Colorspace has been abstracted for greater usability.
+
+
+___
+
+
+
+
+
+Nuke: removing redundant workfile colorspace attributes #5580
+
+Nuke root workfile colorspace data type knobs are long time configured automatically via config roles or the default values are also working well. Therefore there is no need for pipeline managed knobs.
+
+
+___
+
+
+
+
+
+Ftrack: Less verbose logs for Ftrack integration in artist facing logs #5596
+
+- Reduce artist-facing logs for component integration for Ftrack
+- Avoid "Comment is not set" log in artist facing report for Kitsu and Ftrack
+- Remove info log about `ffprobe` inspecting a file (changed to debug log)
+- interesting to see however that it ffprobes the same jpeg twice - but maybe once for thumbnail?
+
+
+___
+
+
+
+### **🐛 Bug fixes**
+
+
+
+Maya: Fix rig validators for new out_SET and controls_SET names #5595
+
+Fix usage of `out_SET` and `controls_SET` since #5310 because they can now be prefixed by the subset name.
+
+
+___
+
+
+
+
+
+TrayPublisher: set default frame values to sequential data #5530
+
+We are inheriting default frame handles and fps data either from project or setting them to 0. This is just for case a production will decide not to injest the sequential representations with asset based metadata.
+
+
+___
+
+
+
+
+
+Publisher: Screenshot opacity value fix #5576
+
+Fix opacity value.
+
+
+___
+
+
+
+
+
+AfterEffects: fix imports of image sequences #5581
+
+#4602 broke imports of image sequences.
+
+
+___
+
+
+
+
+
+AYON: Fix representation context conversion #5591
+
+Do not fix `"folder"` key in representation context until it is needed.
+
+
+___
+
+
+
+
+
+ayon-nuke: default factory to lists #5594
+
+Default factory were missing in settings schemas for complicated objects like lists and it was causing settings to be failing saving.
+
+
+___
+
+
+
+
+
+Maya: Fix look assigner showing no asset if 'not found' representations are present #5597
+
+Fix Maya Look assigner failing to show any content if it finds an invalid container for which it can't find the asset in the current project. (This can happen when e.g. loading something from a library project).There was logic already to avoid this but there was a bug where it used variable `_id` which did not exist and likely had to be `asset_id`.I've fixed that and improved the logged message a bit, e.g.:
+```
+// Warning: openpype.hosts.maya.tools.mayalookassigner.commands : Id found on 22 nodes for which no asset is found database, skipping '641d78ec85c3c5b102e836b0'
+```
+Example not found representation in Loader:The issue isn't necessarily related to NOT FOUND representations but in essence boils down to finding nodes with asset ids that do not exist in the current project which could very well just be local meshes in your scene.**Note:**I've excluded logging the nodes themselves because that tends to be a very long list of nodes. Only downside to removing that is that it's unclear which nodes are related to that `id`. If there are any ideas on how to still provide a concise informational message about that that'd be great so I could add it. Things I had considered:
+- Report the containers, issue here is that it's about asset ids on nodes which don't HAVE to be in containers - it could be local geometry
+- Report the namespaces, issue here is that it could be nodes without namespaces (plus potentially not about ALL nodes in a namespace)
+- Report the short names of the nodes; it's shorter and readable but still likely a lot of nodes.@tokejepsen @LiborBatek any other ideas?
+
+
+___
+
+
+
+
+
+Photoshop: fixed blank Flatten image #5600
+
+Flatten image is simplified publishing approach where all visible layers are "flatten" and published together. This image could be used as a reference etc.This is implemented by auto creator which wasn't updated after first publish. This would result in missing newly created layers after `auto_image` instance was created.
+
+
+___
+
+
+
+
+
+Blender: Remove Hardcoded Subset Name for Reviews #5603
+
+Fixes hardcoded subset name for Reviews in Blender.
+
+
+___
+
+
+
+
+
+TVPaint: Fix tool callbacks #5608
+
+Do not wait for callback to finish.
+
+
+___
+
+
+
+### **🔀 Refactored code**
+
+
+
+Chore: Remove unused variables and cleanup #5588
+
+Removing some unused variables. In some cases the unused variables _seemed like they should've been used - maybe?_ so please **double check the code whether it doesn't hint to an already existing bug**.Also tweaked some other small bugs in code + tweaked logging levels.
+
+
+___
+
+
+
+### **Merged pull requests**
+
+
+
+Chore: Loader log deprecation warning for 'fname' attribute #5587
+
+Since https://github.com/ynput/OpenPype/pull/4602 the `fname` attribute on the `LoaderPlugin` should've been deprecated and set for removal over time. However, no deprecation warning was logged whatsoever and thus one usage appears to have sneaked in (fixed with this PR) and a new one tried to sneak in with a recent PR
+
+
+___
+
+
+
+
+
+
+## [3.16.5](https://github.com/ynput/OpenPype/tree/3.16.5)
+
+
+[Full Changelog](https://github.com/ynput/OpenPype/compare/3.16.4...3.16.5)
+
+### **🆕 New features**
+
+
+
+Attribute Definitions: Multiselection enum def #5547
+
+Added `multiselection` option to `EnumDef`.
+
+
+___
+
+
+
+### **🚀 Enhancements**
+
+
+
+Farm: adding target collector #5494
+
+Enhancing farm publishing workflow.
+
+
+___
+
+
+
+
+
+Maya: Optimize validate plug-in path attributes #5522
+
+- Optimize query (use `cmds.ls` once)
+- Add Select Invalid action
+- Improve validation report
+- Avoid "Unknown object type" errors
+
+
+___
+
+
+
+
+
+Maya: Remove Validate Instance Attributes plug-in #5525
+
+Remove Validate Instance Attributes plug-in.
+
+
+___
+
+
+
+
+
+Enhancement: Tweak logging for artist facing reports #5537
+
+Tweak the logging of publishing for global, deadline, maya and a fusion plugin to have a cleaner artist-facing report.
+- Fix context being reported correctly from CollectContext
+- Fix ValidateMeshArnoldAttributes: fix when arnold is not loaded, fix applying settings, fix for when ai attributes do not exist
+
+
+___
+
+
+
+
+
+AYON: Update settings #5544
+
+Updated settings in AYON addons and conversion of AYON settings in OpenPype.
+
+
+___
+
+
+
+
+
+Chore: Removed Ass export script #5560
+
+Removed Arnold render script, which was obsolete and unused.
+
+
+___
+
+
+
+
+
+Nuke: Allow for knob values to be validated against multiple values. #5042
+
+Knob values can now be validated against multiple values, so you can allow write nodes to be `exr` and `png`, or `16-bit` and `32-bit`.
+
+
+___
+
+
+
+
+
+Enhancement: Cosmetics for Higher version of publish already exists validation error #5190
+
+Fix double spaces in message.Example output **after** the PR:
+
+
+___
+
+
+
+
+
+Nuke: publish existing frames on farm #5409
+
+This PR proposes adding a fourth option in Nuke render publish called "Use Existing Frames - Farm". This would be useful when the farm is busy or when the artist lacks enough farm licenses. Additionally, some artists prefer rendering on the farm but still want to check frames before publishing.By adding the "Use Existing Frames - Farm" option, artists will have more flexibility and control over their render publishing process. This enhancement will streamline the workflow and improve efficiency for Nuke users.
+
+
+___
+
+
+
+
+
+Unreal: Create project in temp location and move to final when done #5476
+
+Create Unreal project in local temporary folder and when done, move it to final destination.
+
+
+___
+
+
+
+
+
+TrayPublisher: adding audio product type into default presets #5489
+
+Adding Audio product type into default presets so anybody can publish audio to their shots.
+
+
+___
+
+
+
+
+
+Global: avoiding cleanup of flagged representation #5502
+
+Publishing folder can be flagged as persistent at representation level.
+
+
+___
+
+
+
+
+
+General: missing tag could raise error #5511
+
+- avoiding potential situation where missing Tag key could raise error
+
+
+___
+
+
+
+
+
+Chore: Queued event system #5514
+
+Implemented event system with more expected behavior of event system. If an event is triggered during other event callback, it is not processed immediately but waits until all callbacks of previous events are done. The event system also allows to not trigger events directly once `emit_event` is called which gives option to process events in custom loops.
+
+
+___
+
+
+
+
+
+Publisher: Tweak log message to provide plugin name after "Plugin" #5521
+
+Fix logged message for settings automatically applied to plugin attributes
+
+
+___
+
+
+
+
+
+Houdini: Improve VDB Selection #5523
+
+Improves VDB selection if selection is `SopNode`: return the selected sop nodeif selection is `ObjNode`: get the output node with the minimum 'outputidx' or the node with display flag
+
+
+___
+
+
+
+
+
+Maya: Refactor/tweak Validate Instance In same Context plug-in #5526
+
+- Chore/Refactor: Re-use existing select invalid and repair actions
+- Enhancement: provide more elaborate PublishValidationError report
+- Bugfix: fix "optional" support by using `OptionalPyblishPluginMixin` base class.
+
+
+___
+
+
+
+
+
+Enhancement: Update houdini main menu #5527
+
+This PR adds two updates:
+- dynamic main menu
+- dynamic asset name and task
+
+
+___
+
+
+
+
+
+Houdini: Reset FPS when clicking Set Frame Range #5528
+
+_Similar to Maya,_ Make `Set Frame Range` resets FPS, issue https://github.com/ynput/OpenPype/issues/5516
+
+
+___
+
+
+
+
+
+Enhancement: Deadline plugins optimize, cleanup and fix optional support for validate deadline pools #5531
+
+- Fix optional support of validate deadline pools
+- Query deadline webservice only once per URL for verification, and once for available deadline pools instead of for every instance
+- Use `deadlineUrl` in `instance.data` when validating pools if it is set.
+- Code cleanup: Re-use existing `requests_get` implementation
+
+
+___
+
+
+
+
+
+Chore: PowerShell script for docker build #5535
+
+Added PowerShell script to run docker build.
+
+
+___
+
+
+
+
+
+AYON: Deadline expand userpaths in executables list #5540
+
+Expande `~` paths in executables list.
+
+
+___
+
+
+
+
+
+Chore: Use correct git url #5542
+
+Fixed github url in README.md.
+
+
+___
+
+
+
+
+
+Chore: Create plugin does not expect system settings #5553
+
+System settings are not passed to initialization of create plugin initialization (and `apply_settings`).
+
+
+___
+
+
+
+
+
+Chore: Allow custom Qt scale factor rounding policy #5555
+
+Do not force `PassThrough` rounding policy if different policy is defined via env variable.
+
+
+___
+
+
+
+
+
+Houdini: Fix outdated containers pop-up on opening last workfile on launch #5567
+
+Fix Houdini not showing outdated containers pop-up on scene open when launching with last workfile argument
+
+
+___
+
+
+
+
+
+Houdini: Improve errors e.g. raise PublishValidationError or cosmetics #5568
+
+Improve errors e.g. raise PublishValidationError or cosmeticsThis also fixes the Increment Current File plug-in since due to an invalid import it was previously broken
+
+
+___
+
+
+
+
+
+Fusion: Code updates #5569
+
+Update fusion code which contains obsolete code. Removed `switch_ui.py` script from fusion with related script in scripts.
+
+
+___
+
+
+
+### **🐛 Bug fixes**
+
+
+
+Maya: Validate Shape Zero fix repair action + provide informational artist-facing report #5524
+
+Refactor to PublishValidationError to allow the RepairAction to work + provide informational report message
+
+
+___
+
+
+
+
+
+Maya: Fix attribute definitions for `CreateYetiCache` #5574
+
+Fix attribute definitions for `CreateYetiCache`
+
+
+___
+
+
+
+
+
+Max: Optional Renderable Camera Validator for Render Instance #5286
+
+Optional validation to check on renderable camera being set up correctly for deadline submission.If not being set up correctly, it wont pass the validation and user can perform repair actions.
+
+
+___
+
+
+
+
+
+Max: Adding custom modifiers back to the loaded objects #5378
+
+The custom parameters OpenpypeData doesn't show in the loaded container when it is being loaded through the loader.
+
+
+___
+
+
+
+
+
+Houdini: Use default_variant to Houdini Node TAB Creator #5421
+
+Use the default variant of the creator plugins on the interactive creator from the TAB node search instead of hard-coding it to `Main`.
+
+
+___
+
+
+
+
+
+Nuke: adding inherited colorspace from instance #5454
+
+Thumbnails are extracted with inherited colorspace collected from rendering write node.
+
+
+___
+
+
+
+
+
+Add kitsu credentials to deadline publish job #5455
+
+This PR hopefully fixes this issue #5440
+
+
+___
+
+
+
+
+
+AYON: Fill entities during editorial #5475
+
+Fill entities and update template data on instances during extract AYON hierarchy.
+
+
+___
+
+
+
+
+
+Ftrack: Fix version 0 when integrating to Ftrack - OP-6595 #5477
+
+Fix publishing version 0 to Ftrack.
+
+
+___
+
+
+
+
+
+OCIO: windows unc path support in Nuke and Hiero #5479
+
+Hiero and Nuke is not supporting windows unc path formatting in OCIO environment variable.
+
+
+___
+
+
+
+
+
+Deadline: Added super call to init #5480
+
+DL 10.3 requires plugin inheriting from DeadlinePlugin to call super's **init** explicitly.
+
+
+___
+
+
+
+
+
+Nuke: fixing thumbnail and monitor out root attributes #5483
+
+Nuke Root Colorspace settings for Thumbnail and Monitor Out schema was gradually changed between version 12, 13, 14 and we needed to address those changes individually for particular version.
+
+
+___
+
+
+
+
+
+Nuke: fixing missing `instance_id` error #5484
+
+Workfiles with Instances created in old publisher workflow were rising error during converting method since they were missing `instance_id` key introduced in new publisher workflow.
+
+
+___
+
+
+
+
+
+Nuke: existing frames validator is repairing render target #5486
+
+Nuke is now correctly repairing render target after the existing frames validator finds missing frames and repair action is used.
+
+
+___
+
+
+
+
+
+added UE to extract burnins families #5487
+
+This PR fixes missing burnins in reviewables when rendering from UE.
+___
+
+
+
+
+
+Harmony: refresh code for current Deadline #5493
+
+- Added support in Deadline Plug-in for new versions of Harmony, in particular version 21 and 22.
+- Remove review=False flag on render instance
+- Add farm=True flag on render instance
+- Fix is_in_tests function call in Harmony Deadline submission plugin
+- Force HarmonyOpenPype.py Deadline Python plug-in to py3
+- Fix cosmetics/hound in HarmonyOpenPype.py Deadline Python plug-in
+
+
+___
+
+
+
+
+
+Publisher: Fix multiselection value #5505
+
+Selection of multiple instances in Publisher does not cause that all instances change all publish attributes to the same value.
+
+
+___
+
+
+
+
+
+Publisher: Avoid warnings on thumbnails if source image also has alpha channel #5510
+
+Avoids the following warning from `ExtractThumbnailFromSource`:
+```
+// pyblish.ExtractThumbnailFromSource : oiiotool WARNING: -o : Can't save 4 channels to jpeg... saving only R,G,B
+```
+
+
+
+___
+
+
+
+
+
+Update ayon-python-api #5512
+
+Update ayon python api and related callbacks.
+
+
+___
+
+
+
+
+
+Max: Fixing the bug of falling back to use workfile for Arnold or any renderers except Redshift #5520
+
+Fix the bug of falling back to use workfile for Arnold
+
+
+___
+
+
+
+
+
+General: Fix Validate Publish Dir Validator #5534
+
+Nonsensical "family" key was used instead of real value (as 'render' etc.) which would result in wrong translation of intermediate family names.Updated docstring.
+
+
+___
+
+
+
+
+
+have the addons loading respect a custom AYON_ADDONS_DIR #5539
+
+When using a custom AYON_ADDONS_DIR environment variable that variable is used in the launcher correctly and downloads and extracts addons to there, however when running Ayon does not respect this environment variable
+
+
+___
+
+
+
+
+
+Deadline: files on representation cannot be single item list #5545
+
+Further logic expects that single item files will be only 'string' not 'list' (eg. repre["files"] = "abc.exr" not repre["files"] = ["abc.exr"].This would cause an issue in ExtractReview later.This could happen if DL rendered single frame file with different frame value.
+
+
+___
+
+
+
+
+
+Webpublisher: better encode list values for click #5546
+
+Targets could be a list, original implementation pushed it as a separate items, it must be added as `--targets webpulish --targets filepublish`.`wepublish_routes` handles triggering from UI, changes in `publish_functions` handle triggering from cmd (for tests, api access).
+
+
+___
+
+
+
+
+
+Houdini: Introduce imprint function for correct version in hda loader #5548
+
+Resolve #5478
+
+
+___
+
+
+
+
+
+AYON: Fill entities during editorial (2) #5549
+
+Fix changes made in https://github.com/ynput/OpenPype/pull/5475.
+
+
+___
+
+
+
+
+
+Max: OP Data updates in Loaders #5563
+
+Fix the bug on the loaders not being able to load the objects when iterating key and values with the dict.Max prefers list over the list in dict.
+
+
+___
+
+
+
+
+
+Create Plugins: Better check of overriden '__init__' method #5571
+
+Create plugins do not log warning messages about each create plugin because of wrong `__init__` method check.
+
+
+___
+
+
+
+### **Merged pull requests**
+
+
+
+Tests: fix unit tests #5533
+
+Fixed failing tests.Updated Unreal's validator to match removed general one which had a couple of issues fixed.
+
+
+___
+
+
+
+
+
+
## [3.16.4](https://github.com/ynput/OpenPype/tree/3.16.4)
diff --git a/README.md b/README.md
index 6caed8061c..ce98f845e6 100644
--- a/README.md
+++ b/README.md
@@ -62,7 +62,7 @@ development tools like [CMake](https://cmake.org/) and [Visual Studio](https://v
#### Clone repository:
```sh
-git clone --recurse-submodules git@github.com:Pypeclub/OpenPype.git
+git clone --recurse-submodules git@github.com:ynput/OpenPype.git
```
#### To build OpenPype:
@@ -144,6 +144,10 @@ sudo ./tools/docker_build.sh centos7
If all is successful, you'll find built OpenPype in `./build/` folder.
+Docker build can be also started from Windows machine, just use `./tools/docker_build.ps1` instead of shell script.
+
+This could be used even for building linux build (with argument `centos7` or `debian`)
+
#### Manual build
You will need [Python >= 3.9](https://www.python.org/downloads/) and [git](https://git-scm.com/downloads). You'll also need [curl](https://curl.se) on systems that doesn't have one preinstalled.
diff --git a/openpype/client/server/conversion_utils.py b/openpype/client/server/conversion_utils.py
index a6c190a0fc..f67a1ef9c4 100644
--- a/openpype/client/server/conversion_utils.py
+++ b/openpype/client/server/conversion_utils.py
@@ -663,10 +663,13 @@ def convert_v4_representation_to_v3(representation):
if isinstance(context, six.string_types):
context = json.loads(context)
- if "folder" in context:
- _c_folder = context.pop("folder")
+ if "asset" not in context and "folder" in context:
+ _c_folder = context["folder"]
context["asset"] = _c_folder["name"]
+ elif "asset" in context and "folder" not in context:
+ context["folder"] = {"name": context["asset"]}
+
if "product" in context:
_c_product = context.pop("product")
context["family"] = _c_product["type"]
@@ -959,9 +962,11 @@ def convert_create_representation_to_v4(representation, con):
converted_representation["files"] = new_files
context = representation["context"]
- context["folder"] = {
- "name": context.pop("asset", None)
- }
+ if "folder" not in context:
+ context["folder"] = {
+ "name": context.get("asset")
+ }
+
context["product"] = {
"type": context.pop("family", None),
"name": context.pop("subset", None),
@@ -1285,7 +1290,7 @@ def convert_update_representation_to_v4(
if "context" in update_data:
context = update_data["context"]
- if "asset" in context:
+ if "folder" not in context and "asset" in context:
context["folder"] = {"name": context.pop("asset")}
if "family" in context or "subset" in context:
diff --git a/openpype/client/server/entities.py b/openpype/client/server/entities.py
index 9579f13add..39322627bb 100644
--- a/openpype/client/server/entities.py
+++ b/openpype/client/server/entities.py
@@ -83,10 +83,10 @@ def _get_subsets(
project_name,
subset_ids,
subset_names,
- folder_ids,
- names_by_folder_ids,
- active,
- fields
+ folder_ids=folder_ids,
+ names_by_folder_ids=names_by_folder_ids,
+ active=active,
+ fields=fields,
):
yield convert_v4_subset_to_v3(subset)
diff --git a/openpype/hooks/pre_ocio_hook.py b/openpype/hooks/pre_ocio_hook.py
index 1307ed9f76..add3a0adaf 100644
--- a/openpype/hooks/pre_ocio_hook.py
+++ b/openpype/hooks/pre_ocio_hook.py
@@ -45,6 +45,9 @@ class OCIOEnvHook(PreLaunchHook):
if config_data:
ocio_path = config_data["path"]
+ if self.host_name in ["nuke", "hiero"]:
+ ocio_path = ocio_path.replace("\\", "/")
+
self.log.info(
f"Setting OCIO environment to config path: {ocio_path}")
diff --git a/openpype/hosts/aftereffects/api/extension.zxp b/openpype/hosts/aftereffects/api/extension.zxp
index 358e9740d3..933dc7dc6c 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/CSXS/manifest.xml b/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml
index 0057758320..7329a9e723 100644
--- a/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml
+++ b/openpype/hosts/aftereffects/api/extension/CSXS/manifest.xml
@@ -1,5 +1,5 @@
-
@@ -10,22 +10,22 @@
-
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
@@ -63,7 +63,7 @@
550
400
-->
-
+
./icons/iconNormal.png
@@ -71,9 +71,9 @@
./icons/iconDisabled.png
./icons/iconDarkNormal.png
./icons/iconDarkRollover.png
-
+
-
\ No newline at end of file
+
diff --git a/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx b/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx
index bc443930df..c00844e637 100644
--- a/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx
+++ b/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx
@@ -215,6 +215,8 @@ function _getItem(item, comps, folders, footages){
* Refactor
*/
var item_type = '';
+ var path = '';
+ var containing_comps = [];
if (item instanceof FolderItem){
item_type = 'folder';
if (!folders){
@@ -222,10 +224,18 @@ function _getItem(item, comps, folders, footages){
}
}
if (item instanceof FootageItem){
- item_type = 'footage';
if (!footages){
return "{}";
}
+ item_type = 'footage';
+ if (item.file){
+ path = item.file.fsName;
+ }
+ if (item.usedIn){
+ for (j = 0; j < item.usedIn.length; ++j){
+ containing_comps.push(item.usedIn[j].id);
+ }
+ }
}
if (item instanceof CompItem){
item_type = 'comp';
@@ -236,7 +246,9 @@ function _getItem(item, comps, folders, footages){
var item = {"name": item.name,
"id": item.id,
- "type": item_type};
+ "type": item_type,
+ "path": path,
+ "containing_comps": containing_comps};
return JSON.stringify(item);
}
diff --git a/openpype/hosts/aftereffects/api/ws_stub.py b/openpype/hosts/aftereffects/api/ws_stub.py
index f5b96fa63a..18f530e272 100644
--- a/openpype/hosts/aftereffects/api/ws_stub.py
+++ b/openpype/hosts/aftereffects/api/ws_stub.py
@@ -37,6 +37,9 @@ class AEItem(object):
height = attr.ib(default=None)
is_placeholder = attr.ib(default=False)
uuid = attr.ib(default=False)
+ path = attr.ib(default=False) # path to FootageItem to validate
+ # list of composition Footage is in
+ containing_comps = attr.ib(factory=list)
class AfterEffectsServerStub():
@@ -704,7 +707,10 @@ class AfterEffectsServerStub():
d.get("instance_id"),
d.get("width"),
d.get("height"),
- d.get("is_placeholder"))
+ d.get("is_placeholder"),
+ d.get("uuid"),
+ d.get("path"),
+ d.get("containing_comps"),)
ret.append(item)
return ret
diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py
index dcf424b44f..fbe600ae68 100644
--- a/openpype/hosts/aftereffects/plugins/create/create_render.py
+++ b/openpype/hosts/aftereffects/plugins/create/create_render.py
@@ -164,7 +164,7 @@ class RenderCreator(Creator):
api.get_stub().rename_item(comp_id,
new_comp_name)
- def apply_settings(self, project_settings, system_settings):
+ def apply_settings(self, project_settings):
plugin_settings = (
project_settings["aftereffects"]["create"]["RenderCreator"]
)
diff --git a/openpype/hosts/aftereffects/plugins/load/load_file.py b/openpype/hosts/aftereffects/plugins/load/load_file.py
index def7c927ab..8d52aac546 100644
--- a/openpype/hosts/aftereffects/plugins/load/load_file.py
+++ b/openpype/hosts/aftereffects/plugins/load/load_file.py
@@ -31,13 +31,8 @@ class FileLoader(api.AfterEffectsLoader):
path = self.filepath_from_context(context)
- repr_cont = context["representation"]["context"]
- if "#" not in path:
- frame = repr_cont.get("frame")
- if frame:
- padding = len(frame)
- path = path.replace(frame, "#" * padding)
- import_options['sequence'] = True
+ if len(context["representation"]["files"]) > 1:
+ import_options['sequence'] = True
if not path:
repr_id = context["representation"]["_id"]
diff --git a/openpype/hosts/aftereffects/plugins/publish/collect_render.py b/openpype/hosts/aftereffects/plugins/publish/collect_render.py
index aa46461915..49874d6cff 100644
--- a/openpype/hosts/aftereffects/plugins/publish/collect_render.py
+++ b/openpype/hosts/aftereffects/plugins/publish/collect_render.py
@@ -138,7 +138,6 @@ class CollectAERender(publish.AbstractCollectRender):
fam = "render.farm"
if fam not in instance.families:
instance.families.append(fam)
- instance.toBeRenderedOn = "deadline"
instance.renderer = "aerender"
instance.farm = True # to skip integrate
if "review" in instance.families:
diff --git a/openpype/hosts/aftereffects/plugins/publish/help/validate_footage_items.xml b/openpype/hosts/aftereffects/plugins/publish/help/validate_footage_items.xml
new file mode 100644
index 0000000000..01c8966015
--- /dev/null
+++ b/openpype/hosts/aftereffects/plugins/publish/help/validate_footage_items.xml
@@ -0,0 +1,14 @@
+
+
+
+Footage item missing
+
+## Footage item missing
+
+ FootageItem `{name}` contains missing `{path}`. Render will not produce any frames and AE will stop react to any integration
+### How to repair?
+
+Remove `{name}` or provide missing file.
+
+
+
diff --git a/openpype/hosts/aftereffects/plugins/publish/validate_footage_items.py b/openpype/hosts/aftereffects/plugins/publish/validate_footage_items.py
new file mode 100644
index 0000000000..40a08a2c3f
--- /dev/null
+++ b/openpype/hosts/aftereffects/plugins/publish/validate_footage_items.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+"""Validate presence of footage items in composition
+Requires:
+"""
+import os
+
+import pyblish.api
+
+from openpype.pipeline import (
+ PublishXmlValidationError
+)
+from openpype.hosts.aftereffects.api import get_stub
+
+
+class ValidateFootageItems(pyblish.api.InstancePlugin):
+ """
+ Validates if FootageItems contained in composition exist.
+
+ AE fails silently and doesn't render anything if footage item file is
+ missing. This will result in nonresponsiveness of AE UI as it expects
+ reaction from user, but it will not provide dialog.
+ This validator tries to check existence of the files.
+ It will not protect from missing frame in multiframes though
+ (as AE api doesn't provide this information and it cannot be told how many
+ frames should be there easily). Missing frame is replaced by placeholder.
+ """
+
+ order = pyblish.api.ValidatorOrder
+ label = "Validate Footage Items"
+ families = ["render.farm", "render.local", "render"]
+ hosts = ["aftereffects"]
+ optional = True
+
+ def process(self, instance):
+ """Plugin entry point."""
+
+ comp_id = instance.data["comp_id"]
+ for footage_item in get_stub().get_items(comps=False, folders=False,
+ footages=True):
+ self.log.info(footage_item)
+ if comp_id not in footage_item.containing_comps:
+ continue
+
+ path = footage_item.path
+ if path and not os.path.exists(path):
+ msg = f"File {path} not found."
+ formatting = {"name": footage_item.name, "path": path}
+ raise PublishXmlValidationError(self, msg,
+ formatting_data=formatting)
diff --git a/openpype/hosts/blender/plugins/load/load_blend.py b/openpype/hosts/blender/plugins/load/load_blend.py
index 99f291a5a7..fa41f4374b 100644
--- a/openpype/hosts/blender/plugins/load/load_blend.py
+++ b/openpype/hosts/blender/plugins/load/load_blend.py
@@ -119,7 +119,7 @@ class BlendLoader(plugin.AssetLoader):
context: Full parenthood of representation to load
options: Additional settings dictionary
"""
- libpath = self.fname
+ libpath = self.filepath_from_context(context)
asset = context["asset"]["name"]
subset = context["subset"]["name"]
diff --git a/openpype/hosts/blender/plugins/load/load_camera_abc.py b/openpype/hosts/blender/plugins/load/load_camera_abc.py
index e5afecff66..05d3fb764d 100644
--- a/openpype/hosts/blender/plugins/load/load_camera_abc.py
+++ b/openpype/hosts/blender/plugins/load/load_camera_abc.py
@@ -100,7 +100,7 @@ class AbcCameraLoader(plugin.AssetLoader):
asset_group = bpy.data.objects.new(group_name, object_data=None)
avalon_container.objects.link(asset_group)
- objects = self._process(libpath, asset_group, group_name)
+ self._process(libpath, asset_group, group_name)
objects = []
nodes = list(asset_group.children)
diff --git a/openpype/hosts/blender/plugins/load/load_camera_fbx.py b/openpype/hosts/blender/plugins/load/load_camera_fbx.py
index b9d05dda0a..3cca6e7fd3 100644
--- a/openpype/hosts/blender/plugins/load/load_camera_fbx.py
+++ b/openpype/hosts/blender/plugins/load/load_camera_fbx.py
@@ -103,7 +103,7 @@ class FbxCameraLoader(plugin.AssetLoader):
asset_group = bpy.data.objects.new(group_name, object_data=None)
avalon_container.objects.link(asset_group)
- objects = self._process(libpath, asset_group, group_name)
+ self._process(libpath, asset_group, group_name)
objects = []
nodes = list(asset_group.children)
diff --git a/openpype/hosts/blender/plugins/publish/collect_review.py b/openpype/hosts/blender/plugins/publish/collect_review.py
index 6459927015..3bf2e39e24 100644
--- a/openpype/hosts/blender/plugins/publish/collect_review.py
+++ b/openpype/hosts/blender/plugins/publish/collect_review.py
@@ -39,15 +39,11 @@ class CollectReview(pyblish.api.InstancePlugin):
]
if not instance.data.get("remove"):
-
- task = instance.context.data["task"]
-
# Store focal length in `burninDataMembers`
burninData = instance.data.setdefault("burninDataMembers", {})
burninData["focalLength"] = focal_length
instance.data.update({
- "subset": f"{task}Review",
"review_camera": camera,
"frameStart": instance.context.data["frameStart"],
"frameEnd": instance.context.data["frameEnd"],
diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py
index f4babc94d3..87159e53f0 100644
--- a/openpype/hosts/blender/plugins/publish/extract_abc.py
+++ b/openpype/hosts/blender/plugins/publish/extract_abc.py
@@ -21,8 +21,6 @@ class ExtractABC(publish.Extractor):
filename = f"{instance.name}.abc"
filepath = os.path.join(stagingdir, filename)
- context = bpy.context
-
# Perform extraction
self.log.info("Performing extraction..")
diff --git a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py
index e141ccaa44..44b2ba3761 100644
--- a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py
+++ b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py
@@ -20,8 +20,6 @@ class ExtractAnimationABC(publish.Extractor):
filename = f"{instance.name}.abc"
filepath = os.path.join(stagingdir, filename)
- context = bpy.context
-
# Perform extraction
self.log.info("Performing extraction..")
diff --git a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py
index a21a59b151..036be7bf3c 100644
--- a/openpype/hosts/blender/plugins/publish/extract_camera_abc.py
+++ b/openpype/hosts/blender/plugins/publish/extract_camera_abc.py
@@ -21,16 +21,11 @@ class ExtractCameraABC(publish.Extractor):
filename = f"{instance.name}.abc"
filepath = os.path.join(stagingdir, filename)
- context = bpy.context
-
# Perform extraction
self.log.info("Performing extraction..")
plugin.deselect_all()
- selected = []
- active = None
-
asset_group = None
for obj in instance:
if obj.get(AVALON_PROPERTY):
diff --git a/openpype/hosts/flame/plugins/load/load_clip.py b/openpype/hosts/flame/plugins/load/load_clip.py
index 338833b449..ca4eab0f63 100644
--- a/openpype/hosts/flame/plugins/load/load_clip.py
+++ b/openpype/hosts/flame/plugins/load/load_clip.py
@@ -48,7 +48,6 @@ class LoadClip(opfapi.ClipLoader):
self.fpd = fproject.current_workspace.desktop
# load clip to timeline and get main variables
- namespace = namespace
version = context['version']
version_data = version.get("data", {})
version_name = version.get("name", None)
diff --git a/openpype/hosts/flame/plugins/load/load_clip_batch.py b/openpype/hosts/flame/plugins/load/load_clip_batch.py
index ca43b94ee9..1f3a017d72 100644
--- a/openpype/hosts/flame/plugins/load/load_clip_batch.py
+++ b/openpype/hosts/flame/plugins/load/load_clip_batch.py
@@ -45,7 +45,6 @@ class LoadClipBatch(opfapi.ClipLoader):
self.batch = options.get("batch") or flame.batch
# load clip to timeline and get main variables
- namespace = namespace
version = context['version']
version_data = version.get("data", {})
version_name = version.get("name", None)
diff --git a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py
index 23fdf5e785..e14f960a2b 100644
--- a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py
+++ b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py
@@ -325,7 +325,6 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
def _create_shot_instance(self, context, clip_name, **data):
master_layer = data.get("heroTrack")
hierarchy_data = data.get("hierarchyData")
- asset = data.get("asset")
if not master_layer:
return
diff --git a/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/backgrounds_selected_to32bit.py b/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/backgrounds_selected_to32bit.py
deleted file mode 100644
index 1a0a9911ea..0000000000
--- a/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/backgrounds_selected_to32bit.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from openpype.hosts.fusion.api import (
- comp_lock_and_undo_chunk,
- get_current_comp
-)
-
-
-def main():
- comp = get_current_comp()
- """Set all selected backgrounds to 32 bit"""
- with comp_lock_and_undo_chunk(comp, 'Selected Backgrounds to 32bit'):
- tools = comp.GetToolList(True, "Background").values()
- for tool in tools:
- tool.Depth = 5
-
-
-main()
diff --git a/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/backgrounds_to32bit.py b/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/backgrounds_to32bit.py
deleted file mode 100644
index c2eea505e5..0000000000
--- a/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/backgrounds_to32bit.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from openpype.hosts.fusion.api import (
- comp_lock_and_undo_chunk,
- get_current_comp
-)
-
-
-def main():
- comp = get_current_comp()
- """Set all backgrounds to 32 bit"""
- with comp_lock_and_undo_chunk(comp, 'Backgrounds to 32bit'):
- tools = comp.GetToolList(False, "Background").values()
- for tool in tools:
- tool.Depth = 5
-
-
-main()
diff --git a/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/loaders_selected_to32bit.py b/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/loaders_selected_to32bit.py
deleted file mode 100644
index 2118767f4d..0000000000
--- a/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/loaders_selected_to32bit.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from openpype.hosts.fusion.api import (
- comp_lock_and_undo_chunk,
- get_current_comp
-)
-
-
-def main():
- comp = get_current_comp()
- """Set all selected loaders to 32 bit"""
- with comp_lock_and_undo_chunk(comp, 'Selected Loaders to 32bit'):
- tools = comp.GetToolList(True, "Loader").values()
- for tool in tools:
- tool.Depth = 5
-
-
-main()
diff --git a/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/loaders_to32bit.py b/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/loaders_to32bit.py
deleted file mode 100644
index 7dd1f66a5e..0000000000
--- a/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/loaders_to32bit.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from openpype.hosts.fusion.api import (
- comp_lock_and_undo_chunk,
- get_current_comp
-)
-
-
-def main():
- comp = get_current_comp()
- """Set all loaders to 32 bit"""
- with comp_lock_and_undo_chunk(comp, 'Loaders to 32bit'):
- tools = comp.GetToolList(False, "Loader").values()
- for tool in tools:
- tool.Depth = 5
-
-
-main()
diff --git a/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/switch_ui.py b/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/switch_ui.py
deleted file mode 100644
index 87322235f5..0000000000
--- a/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/switch_ui.py
+++ /dev/null
@@ -1,200 +0,0 @@
-import os
-import sys
-import glob
-import logging
-
-from qtpy import QtWidgets, QtCore
-
-import qtawesome as qta
-
-from openpype.client import get_assets
-from openpype import style
-from openpype.pipeline import (
- install_host,
- get_current_project_name,
-)
-from openpype.hosts.fusion import api
-from openpype.pipeline.context_tools import get_workdir_from_session
-
-log = logging.getLogger("Fusion Switch Shot")
-
-
-class App(QtWidgets.QWidget):
-
- def __init__(self, parent=None):
-
- ################################################
- # |---------------------| |------------------| #
- # |Comp | |Asset | #
- # |[..][ v]| |[ v]| #
- # |---------------------| |------------------| #
- # | Update existing comp [ ] | #
- # |------------------------------------------| #
- # | Switch | #
- # |------------------------------------------| #
- ################################################
-
- QtWidgets.QWidget.__init__(self, parent)
-
- layout = QtWidgets.QVBoxLayout()
-
- # Comp related input
- comp_hlayout = QtWidgets.QHBoxLayout()
- comp_label = QtWidgets.QLabel("Comp file")
- comp_label.setFixedWidth(50)
- comp_box = QtWidgets.QComboBox()
-
- button_icon = qta.icon("fa.folder", color="white")
- open_from_dir = QtWidgets.QPushButton()
- open_from_dir.setIcon(button_icon)
-
- comp_box.setFixedHeight(25)
- open_from_dir.setFixedWidth(25)
- open_from_dir.setFixedHeight(25)
-
- comp_hlayout.addWidget(comp_label)
- comp_hlayout.addWidget(comp_box)
- comp_hlayout.addWidget(open_from_dir)
-
- # Asset related input
- asset_hlayout = QtWidgets.QHBoxLayout()
- asset_label = QtWidgets.QLabel("Shot")
- asset_label.setFixedWidth(50)
-
- asset_box = QtWidgets.QComboBox()
- asset_box.setLineEdit(QtWidgets.QLineEdit())
- asset_box.setFixedHeight(25)
-
- refresh_icon = qta.icon("fa.refresh", color="white")
- refresh_btn = QtWidgets.QPushButton()
- refresh_btn.setIcon(refresh_icon)
-
- asset_box.setFixedHeight(25)
- refresh_btn.setFixedWidth(25)
- refresh_btn.setFixedHeight(25)
-
- asset_hlayout.addWidget(asset_label)
- asset_hlayout.addWidget(asset_box)
- asset_hlayout.addWidget(refresh_btn)
-
- # Options
- options = QtWidgets.QHBoxLayout()
- options.setAlignment(QtCore.Qt.AlignLeft)
-
- current_comp_check = QtWidgets.QCheckBox()
- current_comp_check.setChecked(True)
- current_comp_label = QtWidgets.QLabel("Use current comp")
-
- options.addWidget(current_comp_label)
- options.addWidget(current_comp_check)
-
- accept_btn = QtWidgets.QPushButton("Switch")
-
- layout.addLayout(options)
- layout.addLayout(comp_hlayout)
- layout.addLayout(asset_hlayout)
- layout.addWidget(accept_btn)
-
- self._open_from_dir = open_from_dir
- self._comps = comp_box
- self._assets = asset_box
- self._use_current = current_comp_check
- self._accept_btn = accept_btn
- self._refresh_btn = refresh_btn
-
- self.setWindowTitle("Fusion Switch Shot")
- self.setLayout(layout)
-
- self.resize(260, 140)
- self.setMinimumWidth(260)
- self.setFixedHeight(140)
-
- self.connections()
-
- # Update ui to correct state
- self._on_use_current_comp()
- self._refresh()
-
- def connections(self):
- self._use_current.clicked.connect(self._on_use_current_comp)
- self._open_from_dir.clicked.connect(self._on_open_from_dir)
- self._refresh_btn.clicked.connect(self._refresh)
- self._accept_btn.clicked.connect(self._on_switch)
-
- def _on_use_current_comp(self):
- state = self._use_current.isChecked()
- self._open_from_dir.setEnabled(not state)
- self._comps.setEnabled(not state)
-
- def _on_open_from_dir(self):
-
- start_dir = get_workdir_from_session()
- comp_file, _ = QtWidgets.QFileDialog.getOpenFileName(
- self, "Choose comp", start_dir)
-
- if not comp_file:
- return
-
- # Create completer
- self.populate_comp_box([comp_file])
- self._refresh()
-
- def _refresh(self):
- # Clear any existing items
- self._assets.clear()
-
- asset_names = self.collect_asset_names()
- completer = QtWidgets.QCompleter(asset_names)
-
- self._assets.setCompleter(completer)
- self._assets.addItems(asset_names)
-
- def _on_switch(self):
-
- if not self._use_current.isChecked():
- file_name = self._comps.itemData(self._comps.currentIndex())
- else:
- comp = api.get_current_comp()
- file_name = comp.GetAttrs("COMPS_FileName")
-
- asset = self._assets.currentText()
-
- import colorbleed.scripts.fusion_switch_shot as switch_shot
- switch_shot.switch(asset_name=asset, filepath=file_name, new=True)
-
- def collect_slap_comps(self, directory):
- items = glob.glob("{}/*.comp".format(directory))
- return items
-
- def collect_asset_names(self):
- project_name = get_current_project_name()
- asset_docs = get_assets(project_name, fields=["name"])
- asset_names = {
- asset_doc["name"]
- for asset_doc in asset_docs
- }
- return list(asset_names)
-
- def populate_comp_box(self, files):
- """Ensure we display the filename only but the path is stored as well
-
- Args:
- files (list): list of full file path [path/to/item/item.ext,]
-
- Returns:
- None
- """
-
- for f in files:
- filename = os.path.basename(f)
- self._comps.addItem(filename, userData=f)
-
-
-if __name__ == '__main__':
- install_host(api)
-
- app = QtWidgets.QApplication(sys.argv)
- window = App()
- window.setStyleSheet(style.load_stylesheet())
- window.show()
- sys.exit(app.exec_())
diff --git a/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/update_loader_ranges.py b/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/update_loader_ranges.py
deleted file mode 100644
index 3d2d1ecfa6..0000000000
--- a/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/update_loader_ranges.py
+++ /dev/null
@@ -1,40 +0,0 @@
-"""Forces Fusion to 'retrigger' the Loader to update.
-
-Warning:
- This might change settings like 'Reverse', 'Loop', trims and other
- settings of the Loader. So use this at your own risk.
-
-"""
-from openpype.hosts.fusion.api.pipeline import (
- get_current_comp,
- comp_lock_and_undo_chunk
-)
-
-
-def update_loader_ranges():
- comp = get_current_comp()
- with comp_lock_and_undo_chunk(comp, "Reload clip time ranges"):
- tools = comp.GetToolList(True, "Loader").values()
- for tool in tools:
-
- # Get tool attributes
- tool_a = tool.GetAttrs()
- clipTable = tool_a['TOOLST_Clip_Name']
- altclipTable = tool_a['TOOLST_AltClip_Name']
- startTime = tool_a['TOOLNT_Clip_Start']
- old_global_in = tool.GlobalIn[comp.CurrentTime]
-
- # Reapply
- for index, _ in clipTable.items():
- time = startTime[index]
- tool.Clip[time] = tool.Clip[time]
-
- for index, _ in altclipTable.items():
- time = startTime[index]
- tool.ProxyFilename[time] = tool.ProxyFilename[time]
-
- tool.GlobalIn[comp.CurrentTime] = old_global_in
-
-
-if __name__ == '__main__':
- update_loader_ranges()
diff --git a/openpype/hosts/fusion/deploy/fusion_shared.prefs b/openpype/hosts/fusion/deploy/fusion_shared.prefs
index b379ea7c66..93b08aa886 100644
--- a/openpype/hosts/fusion/deploy/fusion_shared.prefs
+++ b/openpype/hosts/fusion/deploy/fusion_shared.prefs
@@ -5,7 +5,7 @@ Global = {
Map = {
["OpenPype:"] = "$(OPENPYPE_FUSION)/deploy",
["Config:"] = "UserPaths:Config;OpenPype:Config",
- ["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts;OpenPype:Scripts",
+ ["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts",
},
},
Script = {
diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py
index 04898d0a45..39edca4de3 100644
--- a/openpype/hosts/fusion/plugins/create/create_saver.py
+++ b/openpype/hosts/fusion/plugins/create/create_saver.py
@@ -30,10 +30,6 @@ class CreateSaver(NewCreator):
instance_attributes = [
"reviewable"
]
- default_variants = [
- "Main",
- "Mask"
- ]
# TODO: This should be renamed together with Nuke so it is aligned
temp_rendering_path_template = (
@@ -250,11 +246,7 @@ class CreateSaver(NewCreator):
label="Review",
)
- def apply_settings(
- self,
- project_settings,
- system_settings
- ):
+ def apply_settings(self, project_settings):
"""Method called on initialization of plugin to apply settings."""
# plugin settings
diff --git a/openpype/hosts/fusion/plugins/publish/collect_instances.py b/openpype/hosts/fusion/plugins/publish/collect_instances.py
index 6016baa2a9..4d6da79b77 100644
--- a/openpype/hosts/fusion/plugins/publish/collect_instances.py
+++ b/openpype/hosts/fusion/plugins/publish/collect_instances.py
@@ -85,5 +85,5 @@ class CollectInstanceData(pyblish.api.InstancePlugin):
# Add review family if the instance is marked as 'review'
# This could be done through a 'review' Creator attribute.
if instance.data.get("review", False):
- self.log.info("Adding review family..")
+ self.log.debug("Adding review family..")
instance.data["families"].append("review")
diff --git a/openpype/hosts/fusion/plugins/publish/collect_render.py b/openpype/hosts/fusion/plugins/publish/collect_render.py
index a20a142701..341f3f191a 100644
--- a/openpype/hosts/fusion/plugins/publish/collect_render.py
+++ b/openpype/hosts/fusion/plugins/publish/collect_render.py
@@ -108,7 +108,6 @@ class CollectFusionRender(
fam = "render.farm"
if fam not in instance.families:
instance.families.append(fam)
- instance.toBeRenderedOn = "deadline"
instance.farm = True # to skip integrate
if "review" in instance.families:
# to skip ExtractReview locally
diff --git a/openpype/hosts/harmony/plugins/load/load_template.py b/openpype/hosts/harmony/plugins/load/load_template.py
index f3c69a9104..a78a1bf1ec 100644
--- a/openpype/hosts/harmony/plugins/load/load_template.py
+++ b/openpype/hosts/harmony/plugins/load/load_template.py
@@ -82,7 +82,6 @@ class TemplateLoader(load.LoaderPlugin):
node = harmony.find_node_by_name(node_name, "GROUP")
self_name = self.__class__.__name__
- update_and_replace = False
if is_representation_from_latest(representation):
self._set_green(node)
else:
diff --git a/openpype/hosts/harmony/plugins/publish/collect_farm_render.py b/openpype/hosts/harmony/plugins/publish/collect_farm_render.py
index 5e9b9094a7..af825c052a 100644
--- a/openpype/hosts/harmony/plugins/publish/collect_farm_render.py
+++ b/openpype/hosts/harmony/plugins/publish/collect_farm_render.py
@@ -147,13 +147,13 @@ class CollectFarmRender(publish.AbstractCollectRender):
attachTo=False,
setMembers=[node],
publish=info[4],
- review=False,
renderer=None,
priority=50,
name=node.split("/")[1],
family="render.farm",
families=["render.farm"],
+ farm=True,
resolutionWidth=context.data["resolutionWidth"],
resolutionHeight=context.data["resolutionHeight"],
@@ -174,7 +174,6 @@ class CollectFarmRender(publish.AbstractCollectRender):
outputFormat=info[1],
outputStartFrame=info[3],
leadingZeros=info[2],
- toBeRenderedOn='deadline',
ignoreFrameHandleCheck=True
)
diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py
index 65a4009756..52f96261b2 100644
--- a/openpype/hosts/hiero/api/plugin.py
+++ b/openpype/hosts/hiero/api/plugin.py
@@ -317,20 +317,6 @@ class Spacer(QtWidgets.QWidget):
self.setLayout(layout)
-def get_reference_node_parents(ref):
- """Return all parent reference nodes of reference node
-
- Args:
- ref (str): reference node.
-
- Returns:
- list: The upstream parent reference nodes.
-
- """
- parents = []
- return parents
-
-
class SequenceLoader(LoaderPlugin):
"""A basic SequenceLoader for Resolve
diff --git a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py
index d455ad4a4e..fcb1ab27a0 100644
--- a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py
+++ b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py
@@ -43,7 +43,6 @@ class CollectClipEffects(pyblish.api.InstancePlugin):
if review and review_track_index == _track_index:
continue
for sitem in sub_track_items:
- effect = None
# make sure this subtrack item is relative of track item
if ((track_item not in sitem.linkedItems())
and (len(sitem.linkedItems()) > 0)):
@@ -53,7 +52,6 @@ class CollectClipEffects(pyblish.api.InstancePlugin):
continue
effect = self.add_effect(_track_index, sitem)
-
if effect:
effects.update(effect)
diff --git a/openpype/hosts/houdini/api/colorspace.py b/openpype/hosts/houdini/api/colorspace.py
index 7047644225..cc40b9df1c 100644
--- a/openpype/hosts/houdini/api/colorspace.py
+++ b/openpype/hosts/houdini/api/colorspace.py
@@ -1,7 +1,7 @@
import attr
import hou
from openpype.hosts.houdini.api.lib import get_color_management_preferences
-
+from openpype.pipeline.colorspace import get_display_view_colorspace_name
@attr.s
class LayerMetadata(object):
@@ -54,3 +54,16 @@ class ARenderProduct(object):
)
]
return colorspace_data
+
+
+def get_default_display_view_colorspace():
+ """Returns the colorspace attribute of the default (display, view) pair.
+
+ It's used for 'ociocolorspace' parm in OpenGL Node."""
+
+ prefs = get_color_management_preferences()
+ return get_display_view_colorspace_name(
+ config_path=prefs["config"],
+ display=prefs["display"],
+ view=prefs["view"]
+ )
diff --git a/openpype/hosts/houdini/api/creator_node_shelves.py b/openpype/hosts/houdini/api/creator_node_shelves.py
index 7c6122cffe..1f9fef7417 100644
--- a/openpype/hosts/houdini/api/creator_node_shelves.py
+++ b/openpype/hosts/houdini/api/creator_node_shelves.py
@@ -57,28 +57,31 @@ def create_interactive(creator_identifier, **kwargs):
list: The created instances.
"""
-
- # TODO Use Qt instead
- result, variant = hou.ui.readInput('Define variant name',
- buttons=("Ok", "Cancel"),
- initial_contents='Main',
- title="Define variant",
- help="Set the variant for the "
- "publish instance",
- close_choice=1)
- if result == 1:
- # User interrupted
- return
- variant = variant.strip()
- if not variant:
- raise RuntimeError("Empty variant value entered.")
-
host = registered_host()
context = CreateContext(host)
creator = context.manual_creators.get(creator_identifier)
if not creator:
- raise RuntimeError("Invalid creator identifier: "
- "{}".format(creator_identifier))
+ raise RuntimeError("Invalid creator identifier: {}".format(
+ creator_identifier)
+ )
+
+ # TODO Use Qt instead
+ result, variant = hou.ui.readInput(
+ "Define variant name",
+ buttons=("Ok", "Cancel"),
+ initial_contents=creator.get_default_variant(),
+ title="Define variant",
+ help="Set the variant for the publish instance",
+ close_choice=1
+ )
+
+ if result == 1:
+ # User interrupted
+ return
+
+ variant = variant.strip()
+ if not variant:
+ raise RuntimeError("Empty variant value entered.")
# TODO: Once more elaborate unique create behavior should exist per Creator
# instead of per network editor area then we should move this from here
diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py
index b03f8c8fc1..75c7ff9fee 100644
--- a/openpype/hosts/houdini/api/lib.py
+++ b/openpype/hosts/houdini/api/lib.py
@@ -22,9 +22,12 @@ log = logging.getLogger(__name__)
JSON_PREFIX = "JSON:::"
-def get_asset_fps():
+def get_asset_fps(asset_doc=None):
"""Return current asset fps."""
- return get_current_project_asset()["data"].get("fps")
+
+ if asset_doc is None:
+ asset_doc = get_current_project_asset(fields=["data.fps"])
+ return asset_doc["data"]["fps"]
def set_id(node, unique_id, overwrite=False):
@@ -472,14 +475,19 @@ def maintained_selection():
def reset_framerange():
- """Set frame range to current asset"""
+ """Set frame range and FPS to current asset"""
+ # Get asset data
project_name = get_current_project_name()
asset_name = get_current_asset_name()
# Get the asset ID from the database for the asset of current context
asset_doc = get_asset_by_name(project_name, asset_name)
asset_data = asset_doc["data"]
+ # Get FPS
+ fps = get_asset_fps(asset_doc)
+
+ # Get Start and End Frames
frame_start = asset_data.get("frameStart")
frame_end = asset_data.get("frameEnd")
@@ -493,6 +501,9 @@ def reset_framerange():
frame_start -= int(handle_start)
frame_end += int(handle_end)
+ # Set frame range and FPS
+ print("Setting scene FPS to {}".format(int(fps)))
+ set_scene_fps(fps)
hou.playbar.setFrameRange(frame_start, frame_end)
hou.playbar.setPlaybackRange(frame_start, frame_end)
hou.setFrame(frame_start)
diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py
index 8a26bbb504..c9ae801af5 100644
--- a/openpype/hosts/houdini/api/pipeline.py
+++ b/openpype/hosts/houdini/api/pipeline.py
@@ -25,7 +25,6 @@ from openpype.lib import (
emit_event,
)
-from .lib import get_asset_fps
log = logging.getLogger("openpype.hosts.houdini")
@@ -304,6 +303,28 @@ def on_save():
lib.set_id(node, new_id, overwrite=False)
+def _show_outdated_content_popup():
+ # Get main window
+ parent = lib.get_main_window()
+ if parent is None:
+ log.info("Skipping outdated content pop-up "
+ "because Houdini window can't be found.")
+ else:
+ from openpype.widgets import popup
+
+ # Show outdated pop-up
+ def _on_show_inventory():
+ from openpype.tools.utils import host_tools
+ host_tools.show_scene_inventory(parent=parent)
+
+ dialog = popup.Popup(parent=parent)
+ dialog.setWindowTitle("Houdini scene has outdated content")
+ dialog.setMessage("There are outdated containers in "
+ "your Houdini scene.")
+ dialog.on_clicked.connect(_on_show_inventory)
+ dialog.show()
+
+
def on_open():
if not hou.isUIAvailable():
@@ -317,28 +338,18 @@ def on_open():
lib.validate_fps()
if any_outdated_containers():
- from openpype.widgets import popup
-
- log.warning("Scene has outdated content.")
-
- # Get main window
parent = lib.get_main_window()
if parent is None:
- log.info("Skipping outdated content pop-up "
- "because Houdini window can't be found.")
+ # When opening Houdini with last workfile on launch the UI hasn't
+ # initialized yet completely when the `on_open` callback triggers.
+ # We defer the dialog popup to wait for the UI to become available.
+ # We assume it will open because `hou.isUIAvailable()` returns True
+ import hdefereval
+ hdefereval.executeDeferred(_show_outdated_content_popup)
else:
+ _show_outdated_content_popup()
- # Show outdated pop-up
- def _on_show_inventory():
- from openpype.tools.utils import host_tools
- host_tools.show_scene_inventory(parent=parent)
-
- dialog = popup.Popup(parent=parent)
- dialog.setWindowTitle("Houdini scene has outdated content")
- dialog.setMessage("There are outdated containers in "
- "your Houdini scene.")
- dialog.on_clicked.connect(_on_show_inventory)
- dialog.show()
+ log.warning("Scene has outdated content.")
def on_new():
@@ -385,11 +396,6 @@ def _set_context_settings():
None
"""
- # Set new scene fps
- fps = get_asset_fps()
- print("Setting scene FPS to %i" % fps)
- lib.set_scene_fps(fps)
-
lib.reset_framerange()
diff --git a/openpype/hosts/houdini/api/plugin.py b/openpype/hosts/houdini/api/plugin.py
index 70c837205e..730a627dc3 100644
--- a/openpype/hosts/houdini/api/plugin.py
+++ b/openpype/hosts/houdini/api/plugin.py
@@ -296,7 +296,7 @@ class HoudiniCreator(NewCreator, HoudiniCreatorBase):
"""
return [hou.ropNodeTypeCategory()]
- def apply_settings(self, project_settings, system_settings):
+ def apply_settings(self, project_settings):
"""Method called on initialization of plugin to apply settings."""
settings_name = self.settings_name
diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py
index ab06b30c35..60c34a358b 100644
--- a/openpype/hosts/houdini/plugins/create/create_review.py
+++ b/openpype/hosts/houdini/plugins/create/create_review.py
@@ -3,6 +3,9 @@
from openpype.hosts.houdini.api import plugin
from openpype.lib import EnumDef, BoolDef, NumberDef
+import os
+import hou
+
class CreateReview(plugin.HoudiniCreator):
"""Review with OpenGL ROP"""
@@ -13,7 +16,6 @@ class CreateReview(plugin.HoudiniCreator):
icon = "video-camera"
def create(self, subset_name, instance_data, pre_create_data):
- import hou
instance_data.pop("active", None)
instance_data.update({"node_type": "opengl"})
@@ -82,6 +84,11 @@ class CreateReview(plugin.HoudiniCreator):
instance_node.setParms(parms)
+ # Set OCIO Colorspace to the default output colorspace
+ # if there's OCIO
+ if os.getenv("OCIO"):
+ self.set_colorcorrect_to_default_view_space(instance_node)
+
to_lock = ["id", "family"]
self.lock_parameters(instance_node, to_lock)
@@ -123,3 +130,23 @@ class CreateReview(plugin.HoudiniCreator):
minimum=0.0001,
decimals=3)
]
+
+ def set_colorcorrect_to_default_view_space(self,
+ instance_node):
+ """Set ociocolorspace to the default output space."""
+ from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa
+
+ # set Color Correction parameter to OpenColorIO
+ instance_node.setParms({"colorcorrect": 2})
+
+ # Get default view space for ociocolorspace parm.
+ default_view_space = get_default_display_view_colorspace()
+ instance_node.setParms(
+ {"ociocolorspace": default_view_space}
+ )
+
+ self.log.debug(
+ "'OCIO Colorspace' parm on '{}' has been set to "
+ "the default view color space '{}'"
+ .format(instance_node, default_view_space)
+ )
diff --git a/openpype/hosts/houdini/plugins/create/create_vbd_cache.py b/openpype/hosts/houdini/plugins/create/create_vbd_cache.py
index c015cebd49..9c96e48e3a 100644
--- a/openpype/hosts/houdini/plugins/create/create_vbd_cache.py
+++ b/openpype/hosts/houdini/plugins/create/create_vbd_cache.py
@@ -33,7 +33,7 @@ class CreateVDBCache(plugin.HoudiniCreator):
}
if self.selected_nodes:
- parms["soppath"] = self.selected_nodes[0].path()
+ parms["soppath"] = self.get_sop_node_path(self.selected_nodes[0])
instance_node.setParms(parms)
@@ -42,3 +42,63 @@ class CreateVDBCache(plugin.HoudiniCreator):
hou.ropNodeTypeCategory(),
hou.sopNodeTypeCategory()
]
+
+ def get_sop_node_path(self, selected_node):
+ """Get Sop Path of the selected node.
+
+ Although Houdini allows ObjNode path on `sop_path` for the
+ the ROP node, we prefer it set to the SopNode path explicitly.
+ """
+
+ # Allow sop level paths (e.g. /obj/geo1/box1)
+ if isinstance(selected_node, hou.SopNode):
+ self.log.debug(
+ "Valid SopNode selection, 'SOP Path' in ROP will"
+ " be set to '%s'.", selected_node.path()
+ )
+ return selected_node.path()
+
+ # Allow object level paths to Geometry nodes (e.g. /obj/geo1)
+ # but do not allow other object level nodes types like cameras, etc.
+ elif isinstance(selected_node, hou.ObjNode) and \
+ selected_node.type().name() == "geo":
+
+ # Try to find output node.
+ sop_node = self.get_obj_output(selected_node)
+ if sop_node:
+ self.log.debug(
+ "Valid ObjNode selection, 'SOP Path' in ROP will "
+ "be set to the child path '%s'.", sop_node.path()
+ )
+ return sop_node.path()
+
+ self.log.debug(
+ "Selection isn't valid. 'SOP Path' in ROP will be empty."
+ )
+ return ""
+
+ def get_obj_output(self, obj_node):
+ """Try to find output node.
+
+ If any output nodes are present, return the output node with
+ the minimum 'outputidx'
+ If no output nodes are present, return the node with display flag
+ If no nodes are present at all, return None
+ """
+
+ outputs = obj_node.subnetOutputs()
+
+ # if obj_node is empty
+ if not outputs:
+ return
+
+ # if obj_node has one output child whether its
+ # sop output node or a node with the render flag
+ elif len(outputs) == 1:
+ return outputs[0]
+
+ # if there are more than one, then it has multiple output nodes
+ # return the one with the minimum 'outputidx'
+ else:
+ return min(outputs,
+ key=lambda node: node.evalParm('outputidx'))
diff --git a/openpype/hosts/houdini/plugins/load/load_bgeo.py b/openpype/hosts/houdini/plugins/load/load_bgeo.py
index 22680178c0..489bf944ed 100644
--- a/openpype/hosts/houdini/plugins/load/load_bgeo.py
+++ b/openpype/hosts/houdini/plugins/load/load_bgeo.py
@@ -34,7 +34,6 @@ class BgeoLoader(load.LoaderPlugin):
# Create a new geo node
container = obj.createNode("geo", node_name=node_name)
- is_sequence = bool(context["representation"]["context"].get("frame"))
# Remove the file node, it only loads static meshes
# Houdini 17 has removed the file node from the geo node
diff --git a/openpype/hosts/houdini/plugins/load/load_hda.py b/openpype/hosts/houdini/plugins/load/load_hda.py
index 57edc341a3..9630716253 100644
--- a/openpype/hosts/houdini/plugins/load/load_hda.py
+++ b/openpype/hosts/houdini/plugins/load/load_hda.py
@@ -59,6 +59,9 @@ class HdaLoader(load.LoaderPlugin):
def_paths = [d.libraryFilePath() for d in defs]
new = def_paths.index(file_path)
defs[new].setIsPreferred(True)
+ hda_node.setParms({
+ "representation": str(representation["_id"])
+ })
def remove(self, container):
node = container["node"]
diff --git a/openpype/hosts/houdini/plugins/publish/collect_output_node.py b/openpype/hosts/houdini/plugins/publish/collect_output_node.py
index 601ed17b39..0b27678ed0 100644
--- a/openpype/hosts/houdini/plugins/publish/collect_output_node.py
+++ b/openpype/hosts/houdini/plugins/publish/collect_output_node.py
@@ -1,5 +1,7 @@
import pyblish.api
+from openpype.pipeline.publish import KnownPublishError
+
class CollectOutputSOPPath(pyblish.api.InstancePlugin):
"""Collect the out node's SOP/COP Path value."""
@@ -58,8 +60,8 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin):
elif node_type == "Redshift_Proxy_Output":
out_node = node.parm("RS_archive_sopPath").evalAsNode()
else:
- raise ValueError(
- "ROP node type '%s' is" " not supported." % node_type
+ raise KnownPublishError(
+ "ROP node type '{}' is not supported.".format(node_type)
)
if not out_node:
diff --git a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py
index d4fe37f993..277f922ba4 100644
--- a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py
+++ b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py
@@ -80,14 +80,9 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin):
def get_beauty_render_product(self, prefix, suffix=""):
"""Return the beauty output filename if render element enabled
"""
+ # Remove aov suffix from the product: `prefix.aov_suffix` -> `prefix`
aov_parm = ".{}".format(suffix)
- beauty_product = None
- if aov_parm in prefix:
- beauty_product = prefix.replace(aov_parm, "")
- else:
- beauty_product = prefix
-
- return beauty_product
+ return prefix.replace(aov_parm, "")
def get_render_element_name(self, node, prefix, suffix=""):
"""Return the output filename using the AOV prefix and suffix
diff --git a/openpype/hosts/houdini/plugins/publish/increment_current_file.py b/openpype/hosts/houdini/plugins/publish/increment_current_file.py
index 2493b28bc1..3569de7693 100644
--- a/openpype/hosts/houdini/plugins/publish/increment_current_file.py
+++ b/openpype/hosts/houdini/plugins/publish/increment_current_file.py
@@ -2,7 +2,7 @@ import pyblish.api
from openpype.lib import version_up
from openpype.pipeline import registered_host
-from openpype.action import get_errored_plugins_from_data
+from openpype.pipeline.publish import get_errored_plugins_from_context
from openpype.hosts.houdini.api import HoudiniHost
from openpype.pipeline.publish import KnownPublishError
@@ -27,7 +27,7 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin):
def process(self, context):
- errored_plugins = get_errored_plugins_from_data(context)
+ errored_plugins = get_errored_plugins_from_context(context)
if any(
plugin.__name__ == "HoudiniSubmitPublishDeadline"
for plugin in errored_plugins
@@ -40,9 +40,10 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin):
# Filename must not have changed since collecting
host = registered_host() # type: HoudiniHost
current_file = host.current_file()
- assert (
- context.data["currentFile"] == current_file
- ), "Collected filename mismatches from current scene name."
+ if context.data["currentFile"] != current_file:
+ raise KnownPublishError(
+ "Collected filename mismatches from current scene name."
+ )
new_filepath = version_up(current_file)
host.save_workfile(new_filepath)
diff --git a/openpype/hosts/houdini/plugins/publish/validate_animation_settings.py b/openpype/hosts/houdini/plugins/publish/validate_animation_settings.py
index 4878738ed3..79387fbef5 100644
--- a/openpype/hosts/houdini/plugins/publish/validate_animation_settings.py
+++ b/openpype/hosts/houdini/plugins/publish/validate_animation_settings.py
@@ -1,5 +1,6 @@
import pyblish.api
+from openpype.pipeline.publish import PublishValidationError
from openpype.hosts.houdini.api import lib
import hou
@@ -30,7 +31,7 @@ class ValidateAnimationSettings(pyblish.api.InstancePlugin):
invalid = self.get_invalid(instance)
if invalid:
- raise RuntimeError(
+ raise PublishValidationError(
"Output settings do no match for '%s'" % instance
)
diff --git a/openpype/hosts/houdini/plugins/publish/validate_remote_publish.py b/openpype/hosts/houdini/plugins/publish/validate_remote_publish.py
index 4e8e5fc0e8..4f71d79382 100644
--- a/openpype/hosts/houdini/plugins/publish/validate_remote_publish.py
+++ b/openpype/hosts/houdini/plugins/publish/validate_remote_publish.py
@@ -36,11 +36,11 @@ class ValidateRemotePublishOutNode(pyblish.api.ContextPlugin):
if node.parm("shellexec").eval():
self.raise_error("Must not execute in shell")
if node.parm("prerender").eval() != cmd:
- self.raise_error(("REMOTE_PUBLISH node does not have "
- "correct prerender script."))
+ self.raise_error("REMOTE_PUBLISH node does not have "
+ "correct prerender script.")
if node.parm("lprerender").eval() != "python":
- self.raise_error(("REMOTE_PUBLISH node prerender script "
- "type not set to 'python'"))
+ self.raise_error("REMOTE_PUBLISH node prerender script "
+ "type not set to 'python'")
@classmethod
def repair(cls, context):
@@ -48,5 +48,4 @@ class ValidateRemotePublishOutNode(pyblish.api.ContextPlugin):
lib.create_remote_publish_node(force=True)
def raise_error(self, message):
- self.log.error(message)
- raise PublishValidationError(message, title=self.label)
+ raise PublishValidationError(message)
diff --git a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py
new file mode 100644
index 0000000000..03ecd1b052
--- /dev/null
+++ b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+import pyblish.api
+from openpype.pipeline import (
+ PublishValidationError,
+ OptionalPyblishPluginMixin
+)
+from openpype.pipeline.publish import RepairAction
+from openpype.hosts.houdini.api.action import SelectROPAction
+
+import os
+import hou
+
+
+class SetDefaultViewSpaceAction(RepairAction):
+ label = "Set default view colorspace"
+ icon = "mdi.monitor"
+
+
+class ValidateReviewColorspace(pyblish.api.InstancePlugin,
+ OptionalPyblishPluginMixin):
+ """Validate Review Colorspace parameters.
+
+ It checks if 'OCIO Colorspace' parameter was set to valid value.
+ """
+
+ order = pyblish.api.ValidatorOrder + 0.1
+ families = ["review"]
+ hosts = ["houdini"]
+ label = "Validate Review Colorspace"
+ actions = [SetDefaultViewSpaceAction, SelectROPAction]
+
+ optional = True
+
+ def process(self, instance):
+
+ if not self.is_active(instance.data):
+ return
+
+ if os.getenv("OCIO") is None:
+ self.log.debug(
+ "Using Houdini's Default Color Management, "
+ " skipping check.."
+ )
+ return
+
+ rop_node = hou.node(instance.data["instance_node"])
+ if rop_node.evalParm("colorcorrect") != 2:
+ # any colorspace settings other than default requires
+ # 'Color Correct' parm to be set to 'OpenColorIO'
+ raise PublishValidationError(
+ "'Color Correction' parm on '{}' ROP must be set to"
+ " 'OpenColorIO'".format(rop_node.path())
+ )
+
+ if rop_node.evalParm("ociocolorspace") not in \
+ hou.Color.ocio_spaces():
+
+ raise PublishValidationError(
+ "Invalid value: Colorspace name doesn't exist.\n"
+ "Check 'OCIO Colorspace' parameter on '{}' ROP"
+ .format(rop_node.path())
+ )
+
+ @classmethod
+ def repair(cls, instance):
+ """Set Default View Space Action.
+
+ It is a helper action more than a repair action,
+ used to set colorspace on opengl node to the default view.
+ """
+ from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa
+
+ rop_node = hou.node(instance.data["instance_node"])
+
+ if rop_node.evalParm("colorcorrect") != 2:
+ rop_node.setParms({"colorcorrect": 2})
+ cls.log.debug(
+ "'Color Correction' parm on '{}' has been set to"
+ " 'OpenColorIO'".format(rop_node.path())
+ )
+
+ # Get default view colorspace name
+ default_view_space = get_default_display_view_colorspace()
+
+ rop_node.setParms({"ociocolorspace": default_view_space})
+ cls.log.info(
+ "'OCIO Colorspace' parm on '{}' has been set to "
+ "the default view color space '{}'"
+ .format(rop_node, default_view_space)
+ )
diff --git a/openpype/hosts/houdini/plugins/publish/validate_usd_render_product_names.py b/openpype/hosts/houdini/plugins/publish/validate_usd_render_product_names.py
index 02c44ab94e..1daa96f2b9 100644
--- a/openpype/hosts/houdini/plugins/publish/validate_usd_render_product_names.py
+++ b/openpype/hosts/houdini/plugins/publish/validate_usd_render_product_names.py
@@ -24,7 +24,7 @@ class ValidateUSDRenderProductNames(pyblish.api.InstancePlugin):
if not os.path.isabs(filepath):
invalid.append(
- "Output file path is not " "absolute path: %s" % filepath
+ "Output file path is not absolute path: %s" % filepath
)
if invalid:
diff --git a/openpype/hosts/houdini/startup/MainMenuCommon.xml b/openpype/hosts/houdini/startup/MainMenuCommon.xml
index 47a4653d5d..5818a117eb 100644
--- a/openpype/hosts/houdini/startup/MainMenuCommon.xml
+++ b/openpype/hosts/houdini/startup/MainMenuCommon.xml
@@ -2,7 +2,19 @@