Merge branch 'develop' into feature/OP-2637_Houdini-Farm-publishingrendering

This commit is contained in:
Kayla Man 2023-04-17 14:26:14 +08:00
commit 5223502f89
83 changed files with 4018 additions and 493 deletions

View file

@ -1,6 +1,6 @@
name: Bug Report
description: File a bug report
title: 'bug: '
title: 'Bug: '
labels:
- 'type: bug'
body:
@ -35,31 +35,106 @@ body:
label: Version
description: What version are you running? Look to OpenPype Tray
options:
- 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
- 3.15.2
- 3.15.2-nightly.6
- 3.15.2-nightly.5
- 3.15.2-nightly.4
- 3.15.2-nightly.3
- 3.15.2-nightly.2
- 3.15.2-nightly.1
- 3.15.1
- 3.15.1-nightly.6
- 3.15.1-nightly.5
- 3.15.1-nightly.4
- 3.15.1-nightly.3
- 3.15.1-nightly.2
- 3.15.1-nightly.1
- 3.15.0
- 3.15.0-nightly.1
- 3.14.11-nightly.4
- 3.14.11-nightly.3
- 3.14.11-nightly.2
- 3.14.11-nightly.1
- 3.14.10
- 3.14.10-nightly.9
- 3.14.10-nightly.8
- 3.14.10-nightly.7
- 3.14.10-nightly.6
- 3.14.10-nightly.5
- 3.14.10-nightly.4
- 3.14.10-nightly.3
- 3.14.10-nightly.2
- 3.14.10-nightly.1
- 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
- 3.14.7-nightly.8
- 3.14.7-nightly.7
- 3.14.7-nightly.6
- 3.14.7-nightly.5
- 3.14.7-nightly.4
- 3.14.7-nightly.3
- 3.14.7-nightly.2
- 3.14.7-nightly.1
- 3.14.6
- 3.14.6-nightly.3
- 3.14.6-nightly.2
- 3.14.6-nightly.1
- 3.14.5
- 3.14.5-nightly.3
- 3.14.5-nightly.2
- 3.14.5-nightly.1
- 3.14.4
- 3.14.4-nightly.4
- 3.14.4-nightly.3
- 3.14.4-nightly.2
- 3.14.4-nightly.1
- 3.14.3
- 3.14.3-nightly.7
- 3.14.3-nightly.6
- 3.14.3-nightly.5
- 3.14.3-nightly.4
- 3.14.3-nightly.3
- 3.14.3-nightly.2
- 3.14.3-nightly.1
- 3.14.2
- 3.14.2-nightly.5
- 3.14.2-nightly.4
- 3.14.2-nightly.3
- 3.14.2-nightly.2
- 3.14.2-nightly.1
- 3.14.1
- 3.14.1-nightly.4
- 3.14.1-nightly.3
- 3.14.1-nightly.2
- 3.14.1-nightly.1
- 3.14.0
- 3.14.0-nightly.1
- 3.13.1-nightly.3
- 3.13.1-nightly.2
- 3.13.1-nightly.1
- 3.13.0
- 3.12.2
- 3.12.1
- 3.12.0
- 3.11.1
- 3.11.0
- 3.10.0
- 3.9.8
- 3.9.7
- 3.9.6
- 3.13.0-nightly.1
- 3.12.3-nightly.3
- 3.12.3-nightly.2
- 3.12.3-nightly.1
validations:
required: true
- type: dropdown
@ -88,11 +163,11 @@ body:
required: true
- type: checkboxes
attributes:
label: Is there any more labels you wish to add?
label: Are there any labels you wish to add?
description: Please search labels and identify those related to your bug.
options:
- label: I have searched labels and added any
required: true
- label: I have added the relevant labels to the bug report.
required: true
- type: textarea
id: logs
attributes:

View file

@ -1,5 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Ynput Community Discussions
url: https://community.ynput.io
about: Please ask and answer questions here.
- name: Ynput Discord Server
url: https://discord.gg/ynput
about: For community quick chats.

View file

@ -1,6 +1,6 @@
name: Enhancement Request
description: Create a report to help us enhance a particular feature
title: "enhancement: "
title: "Enhancement: "
labels:
- "type: enhancement"
body:
@ -13,28 +13,28 @@ body:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the bug you encountered.
options:
- label: I have searched the existing issues
- label: I have searched the existing issues.
required: true
- type: textarea
id: related-feature
attributes:
label: Please state which of feature you have in mind and describe what are its shortcomings?
label: Please describe the feature you have in mind and explain what the current shortcomings are?
description: A clear and concise description of what the problem is.
validations:
required: true
- type: textarea
id: enhancement-proposal
attributes:
label: How would you imagine the enhancement of the feature?
label: How would you imagine the implementation of the feature?
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: checkboxes
attributes:
label: Is there any more labels you wish to add?
label: Are there any labels you wish to add?
description: Please search labels and identify those related to your enhancement.
options:
- label: I have searched labels and added any
- label: I have added the relevant labels to the enhancement request.
required: true
- type: textarea
id: alternatives

View file

@ -45,3 +45,6 @@ jobs:
token: ${{ secrets.YNPUT_BOT_TOKEN }}
user_email: ${{ secrets.CI_EMAIL }}
user_name: ${{ secrets.CI_USER }}
cu_api_key: ${{ secrets.CLICKUP_API_KEY }}
cu_team_id: ${{ secrets.CLICKUP_TEAM_ID }}
cu_field_id: ${{ secrets.CLICKUP_RELEASE_FIELD_ID }}

View file

@ -15,11 +15,11 @@ jobs:
with:
ref: ${{ github.event.release.target_commitish }}
- name: Update version
uses: ShaMan123/gha-populate-form-version@v2.0.2
uses: ynput/gha-populate-form-version@main
with:
github_token: ${{ secrets.YNPUT_BOT_TOKEN }}
registry: github
dropdown: _version
limit_to: 25
limit_to: 100
form: .github/ISSUE_TEMPLATE/bug_report.yml
commit_message: 'chore(): update bug report / version'
commit_message: 'chore(): update bug report / version'

View file

@ -1,5 +1,948 @@
# Changelog
## [3.15.4](https://github.com/ynput/OpenPype/tree/3.15.4)
[Full Changelog](https://github.com/ynput/OpenPype/compare/3.15.3...3.15.4)
### **🆕 New features**
<details>
<summary>Maya: Cant assign shaders to the ass file - OP-4859 <a href="https://github.com/ynput/OpenPype/pull/4460">#4460</a></summary>
<strong>Support AiStandIn nodes for look assignment.
</strong>Using operators we assign shaders and attribute/parameters to nodes within standins. Initially there is only support for a limited mount of attributes but we can add support as needed;
```
primaryVisibility
castsShadows
receiveShadows
aiSelfShadows
aiOpaque
aiMatte
aiVisibleInDiffuseTransmission
aiVisibleInSpecularTransmission
aiVisibleInVolume
aiVisibleInDiffuseReflection
aiVisibleInSpecularReflection
aiSubdivUvSmoothing
aiDispHeight
aiDispPadding
aiDispZeroValue
aiStepSize
aiVolumePadding
aiSubdivType
aiSubdivIterations
```
___
</details>
<details>
<summary>Maya: GPU cache representation <a href="https://github.com/ynput/OpenPype/pull/4649">#4649</a></summary>
Implement GPU cache for model, animation and pointcache.
___
</details>
<details>
<summary>Houdini: Implement review family with opengl node <a href="https://github.com/ynput/OpenPype/pull/3839">#3839</a></summary>
<strong>Implements a first pass for Reviews publishing in Houdini. Resolves #2720
</strong>Uses the `opengl` ROP node to produce PNG images.
___
</details>
<details>
<summary>Maya: Camera focal length visible in review - OP-3278 <a href="https://github.com/ynput/OpenPype/pull/4531">#4531</a></summary>
<strong>Camera focal length visible in review.
</strong>Support camera focal length in review; static and dynamic.Resolves #3220
___
</details>
<details>
<summary>Maya: Defining plugins to load on Maya start - OP-4994 <a href="https://github.com/ynput/OpenPype/pull/4714">#4714</a></summary>
Feature to define plugins to load on Maya launch.
___
</details>
<details>
<summary>Nuke, DL: Returning Suspended Publishing attribute <a href="https://github.com/ynput/OpenPype/pull/4715">#4715</a></summary>
Old Nuke Publisher's feature for suspended publishing job on render farm was added back to the current Publisher.
___
</details>
<details>
<summary>Settings UI: Allow setting a size hint for text fields <a href="https://github.com/ynput/OpenPype/pull/4821">#4821</a></summary>
Text entity have `minimum_lines_count` which allows to change minimum size hint of UI input.
___
</details>
<details>
<summary>TrayPublisher: Move 'BatchMovieCreator' settings to 'create' subcategory <a href="https://github.com/ynput/OpenPype/pull/4827">#4827</a></summary>
Moved settings for `BatchMoviewCreator` into subcategory `create` in settings. Changes are made to match other hosts settings chema and structure.
___
</details>
### **🚀 Enhancements**
<details>
<summary>Maya looks: support for native Redshift texture format <a href="https://github.com/ynput/OpenPype/pull/2971">#2971</a></summary>
<strong>Add support for native Redshift textures handling. Closes #2599
</strong>Uses Redshift's Texture Processor executable to convert textures being used in renders to the Redshift ".rstexbin" format.
___
</details>
<details>
<summary>Maya: custom namespace for references <a href="https://github.com/ynput/OpenPype/pull/4511">#4511</a></summary>
<strong>Adding an option in Project Settings > Maya > Loader plugins to set custom namespace. If no namespace is set, the default one is used.
</strong>
___
</details>
<details>
<summary>Maya: Set correct framerange with handles on file opening <a href="https://github.com/ynput/OpenPype/pull/4664">#4664</a></summary>
Set the range of playback from the asset data, counting handles, to get the correct data when calling the "collect_animation_data" function.
___
</details>
<details>
<summary>Maya: Fix camera update <a href="https://github.com/ynput/OpenPype/pull/4751">#4751</a></summary>
Fix resetting any modelPanel to a different camera when loading a camera and updating.
___
</details>
<details>
<summary>Maya: Remove single assembly validation for animation instances <a href="https://github.com/ynput/OpenPype/pull/4840">#4840</a></summary>
Rig groups may now be parented to others groups when `includeParentHierarchy` attribute on the instance is "off".
___
</details>
<details>
<summary>Maya: Optional control of display lights on playblast. <a href="https://github.com/ynput/OpenPype/pull/4145">#4145</a></summary>
<strong>Optional control of display lights on playblast.
</strong>Giving control to what display lights are on the playblasts.
___
</details>
<details>
<summary>Kitsu: note family requirements <a href="https://github.com/ynput/OpenPype/pull/4551">#4551</a></summary>
<strong>Allowing to add family requirements to `IntegrateKitsuNote` task status change.
</strong>Adds a `Family requirements` setting to `Integrate Kitsu Note`, so you can add requirements to determine if kitsu task status should be changed based on which families are published or not. For instance you could have the status change only if another subset than workfile is published (but workfile can still be included) by adding an item set to `Not equal` and `workfile`.
___
</details>
<details>
<summary>Deactivate closed Kitsu projects on OP <a href="https://github.com/ynput/OpenPype/pull/4619">#4619</a></summary>
Deactivate project on OP when the project is closed on Kitsu.
___
</details>
<details>
<summary>Maya: Suggestion to change capture labels. <a href="https://github.com/ynput/OpenPype/pull/4691">#4691</a></summary>
Change capture labels.
___
</details>
<details>
<summary>Houdini: Change node type for OpenPypeContext `null` -> `subnet` <a href="https://github.com/ynput/OpenPype/pull/4745">#4745</a></summary>
Change the node type for OpenPype's hidden context node in Houdini from `null` to `subnet`. This fixes #4734
___
</details>
<details>
<summary>General: Extract burnin hosts filters <a href="https://github.com/ynput/OpenPype/pull/4749">#4749</a></summary>
Removed hosts filter from ExtractBurnin plugin. Instance without representations won't cause crash but just skip the instance. We've discovered because Blender already has review but did not create burnins.
___
</details>
<details>
<summary>Global: Improve speed of Collect Custom Staging Directory <a href="https://github.com/ynput/OpenPype/pull/4768">#4768</a></summary>
Improve speed of Collect Custom Staging Directory.
___
</details>
<details>
<summary>General: Anatomy templates formatting <a href="https://github.com/ynput/OpenPype/pull/4773">#4773</a></summary>
Added option to format only single template from anatomy instead of formatting all of them all the time. Formatting of all templates is causing slowdowns e.g. during publishing of hundreds of instances.
___
</details>
<details>
<summary>Harmony: Handle zip files with deeper structure <a href="https://github.com/ynput/OpenPype/pull/4782">#4782</a></summary>
External Harmony zip files might contain one additional level with scene name.
___
</details>
<details>
<summary>Unreal: Use common logic to configure executable <a href="https://github.com/ynput/OpenPype/pull/4788">#4788</a></summary>
Unreal Editor location and version was autodetected. This easied configuration in some cases but was not flexible enought. This PR is changing the way Unreal Editor location is set, unifying it with the logic other hosts are using.
___
</details>
<details>
<summary>Github: Grammar tweaks + uppercase issue title <a href="https://github.com/ynput/OpenPype/pull/4813">#4813</a></summary>
Tweak some of the grammar in the issue form templates.
___
</details>
<details>
<summary>Houdini: Allow creation of publish instances via Houdini TAB menu <a href="https://github.com/ynput/OpenPype/pull/4831">#4831</a></summary>
Register the available Creator's as houdini tools so an artist can add publish instances via the Houdini TAB node search menu from within the network editor.
___
</details>
### **🐛 Bug fixes**
<details>
<summary>Maya: Fix Collect Render for V-Ray, Redshift and Renderman for missing colorspace <a href="https://github.com/ynput/OpenPype/pull/4650">#4650</a></summary>
Fix Collect Render not working for Redshift, V-Ray and Renderman due to missing `colorspace` argument to `RenderProduct` dataclass.
___
</details>
<details>
<summary>Maya: Xgen fixes <a href="https://github.com/ynput/OpenPype/pull/4707">#4707</a></summary>
Fix for Xgen extraction of world parented nodes and validation for required namespace.
___
</details>
<details>
<summary>Maya: Fix extract review and thumbnail for Maya 2020 <a href="https://github.com/ynput/OpenPype/pull/4744">#4744</a></summary>
Fix playblasting in Maya 2020 with override viewport options enabled. Fixes #4730.
___
</details>
<details>
<summary>Maya: local variable 'arnold_standins' referenced before assignment - OP-5542 <a href="https://github.com/ynput/OpenPype/pull/4778">#4778</a></summary>
MayaLookAssigner erroring when MTOA is not loaded:
```
# Traceback (most recent call last):
# File "\openpype\hosts\maya\tools\mayalookassigner\app.py", line 272, in on_process_selected
# nodes = list(set(item["nodes"]).difference(arnold_standins))
# UnboundLocalError: local variable 'arnold_standins' referenced before assignment
```
___
</details>
<details>
<summary>Maya: Fix getting view and display in Maya 2020 - OP-5035 <a href="https://github.com/ynput/OpenPype/pull/4795">#4795</a></summary>
The `view_transform` returns a different format in Maya 2020. Fixes #4540 (hopefully).
___
</details>
<details>
<summary>Maya: Fix Look Maya 2020 Py2 support for Extract Look <a href="https://github.com/ynput/OpenPype/pull/4808">#4808</a></summary>
Fix Extract Look supporting python 2.7 for Maya 2020.
___
</details>
<details>
<summary>Maya: Fix Validate Mesh Overlapping UVs plugin <a href="https://github.com/ynput/OpenPype/pull/4816">#4816</a></summary>
Fix typo in the code where a maya command returns a `list` instead of `str`.
___
</details>
<details>
<summary>Maya: Fix tile rendering with Vray - OP-5566 <a href="https://github.com/ynput/OpenPype/pull/4832">#4832</a></summary>
Fixes tile rendering with Vray.
___
</details>
<details>
<summary>Deadline: checking existing frames fails when there is number in file name <a href="https://github.com/ynput/OpenPype/pull/4698">#4698</a></summary>
Previous implementation of validator failed on files with any other number in rendered file names.Used regular expression pattern now handles numbers in the file names (eg "Main_beauty.v001.1001.exr", "Main_beauty_v001.1001.exr", "Main_beauty.1001.1001.exr") but not numbers behind frames (eg. "Main_beauty.1001.v001.exr")
___
</details>
<details>
<summary>Maya: Validate Render Settings. <a href="https://github.com/ynput/OpenPype/pull/4735">#4735</a></summary>
Fixes error message when using attribute validation.
___
</details>
<details>
<summary>General: Hero version sites recalculation <a href="https://github.com/ynput/OpenPype/pull/4737">#4737</a></summary>
Sites recalculation in integrate hero version did expect that it is integrated exactly same amount of files as in previous integration. This is not the case in many cases, so the sites recalculation happens in a different way, first are prepared all sites from previous representation files, and all of them are added to each file in new representation.
___
</details>
<details>
<summary>Houdini: Fix collect current file <a href="https://github.com/ynput/OpenPype/pull/4739">#4739</a></summary>
Fixes the Workfile publishing getting added into every instance being published from Houdini
___
</details>
<details>
<summary>Global: Fix Extract Burnin + Colorspace functions for conflicting python environments with PYTHONHOME <a href="https://github.com/ynput/OpenPype/pull/4740">#4740</a></summary>
This fixes the running of openpype processes from e.g. a host with conflicting python versions that had `PYTHONHOME` said additionally to `PYTHONPATH`, like e.g. Houdini Py3.7 together with OpenPype Py3.9 when using Extract Burnin for a review in #3839This fix applies to Extract Burnin and some of the colorspace functions that use `run_openpype_process`
___
</details>
<details>
<summary>Harmony: render what is in timeline in Harmony locally <a href="https://github.com/ynput/OpenPype/pull/4741">#4741</a></summary>
Previously it wasn't possible to render according to what was set in Timeline in scene start/end, just by what it was set in whole timeline.This allows artist to override what is in DB with what they require (with disabled `Validate Scene Settings`). Now artist can extend scene by additional frames, that shouldn't be rendered, but which might be desired.Removed explicit set scene settings (eg. applying frames and resolution directly to the scene after launch), added separate menu item to allow artist to do it themselves.
___
</details>
<details>
<summary>Maya: Extract Review settings add Use Background Gradient <a href="https://github.com/ynput/OpenPype/pull/4747">#4747</a></summary>
Add Display Gradient Background toggle in settings to fix support for setting flat background color for reviews.
___
</details>
<details>
<summary>Nuke: publisher is offering review on write families on demand <a href="https://github.com/ynput/OpenPype/pull/4755">#4755</a></summary>
Original idea where reviewable toggle will be offered in publisher on demand is fixed and now `review` attribute can be disabled in settings.
___
</details>
<details>
<summary>Workfiles: keep Browse always enabled <a href="https://github.com/ynput/OpenPype/pull/4766">#4766</a></summary>
Browse might make sense even if there are no workfiles present, actually in that case it makes the most sense (eg. I want to locate workfile from outside - from Desktop for example).
___
</details>
<details>
<summary>Global: label key in instance data is optional <a href="https://github.com/ynput/OpenPype/pull/4779">#4779</a></summary>
Collect OTIO review plugin is not crashing if `label` key is missing in instance data.
___
</details>
<details>
<summary>Loader: Fix missing variable <a href="https://github.com/ynput/OpenPype/pull/4781">#4781</a></summary>
There is missing variable `handles` in loader tool after https://github.com/ynput/OpenPype/pull/4746. The variable was renamed to `handles_label` and is initialized to `None` if handles are not available.
___
</details>
<details>
<summary>Nuke: Workfile Template builder fixes <a href="https://github.com/ynput/OpenPype/pull/4783">#4783</a></summary>
Popup window after Nuke start is not showing. Knobs with X/Y coordination on nodes where were converted from placeholders are not added if `keepPlaceholders` is witched off.
___
</details>
<details>
<summary>Maya: Add family filter 'review' to burnin profile with focal length <a href="https://github.com/ynput/OpenPype/pull/4791">#4791</a></summary>
Avoid profile burnin with `focalLength` key for renders, but use only for playblast reviews.
___
</details>
<details>
<summary>add farm instance to the render collector in 3dsMax <a href="https://github.com/ynput/OpenPype/pull/4794">#4794</a></summary>
bug fix for the failure of submitting publish job in 3dsmax
___
</details>
<details>
<summary>Publisher: Plugin active attribute is respected <a href="https://github.com/ynput/OpenPype/pull/4798">#4798</a></summary>
Publisher consider plugin's `active` attribute, so the plugin is not processed when `active` is set to `False`. But we use the attribute in `OptionalPyblishPluginMixin` for different purposes, so I've added hack bypass of the active state validation when plugin inherit from the mixin. This is temporary solution which cannot be changed until all hosts use Publisher otherwise global plugins would be broken. Also plugins which have `enabled` set to `False` are filtered out -> this happened only when automated settings were applied and the settings contained `"enabled"` key se to `False`.
___
</details>
<details>
<summary>Nuke: settings and optional attribute in publisher for some validators <a href="https://github.com/ynput/OpenPype/pull/4811">#4811</a></summary>
New publisher is supporting optional switch for plugins which is offered in Publisher in Right panel. Some plugins were missing this switch and also settings which would offer the optionality.
___
</details>
<details>
<summary>Settings: Version settings popup fix <a href="https://github.com/ynput/OpenPype/pull/4822">#4822</a></summary>
Version completer popup have issues on some platforms, this should fix those edge cases. Also fixed issue when completer stayed shown fater reset (save).
___
</details>
<details>
<summary>Hiero/Nuke: adding monitorOut key to settings <a href="https://github.com/ynput/OpenPype/pull/4826">#4826</a></summary>
New versions of Hiero were introduced with new colorspace property for Monitor Out. It have been added into project settings. Also added new config names into settings enumerator option.
___
</details>
<details>
<summary>Nuke: removed default workfile template builder preset <a href="https://github.com/ynput/OpenPype/pull/4835">#4835</a></summary>
Default for workfile template builder should have been empty.
___
</details>
<details>
<summary>TVPaint: Review can be made from any instance <a href="https://github.com/ynput/OpenPype/pull/4843">#4843</a></summary>
Add `"review"` tag to output of extract sequence if instance is marked for review. At this moment only instances with family `"review"` were able to define input for `ExtractReview` plugin which is not right.
___
</details>
### **🔀 Refactored code**
<details>
<summary>Deadline: Remove unused FramesPerTask job info submission <a href="https://github.com/ynput/OpenPype/pull/4657">#4657</a></summary>
Remove unused `FramesPerTask` job info submission to Deadline.
___
</details>
<details>
<summary>Maya: Remove pymel dependency <a href="https://github.com/ynput/OpenPype/pull/4724">#4724</a></summary>
Refactors code written using `pymel` to use standard maya python libraries instead like `maya.cmds` or `maya.api.OpenMaya`
___
</details>
<details>
<summary>Remove "preview" data from representation <a href="https://github.com/ynput/OpenPype/pull/4759">#4759</a></summary>
Remove "preview" data from representation
___
</details>
<details>
<summary>Maya: Collect Review cleanup code for attached subsets <a href="https://github.com/ynput/OpenPype/pull/4720">#4720</a></summary>
Refactor some code for Maya: Collect Review for attached subsets.
___
</details>
<details>
<summary>Refactor: Remove `handles`, `edit_in` and `edit_out` backwards compatibility <a href="https://github.com/ynput/OpenPype/pull/4746">#4746</a></summary>
Removes backward compatibiliy fallback for data called `handles`, `edit_in` and `edit_out`.
___
</details>
### **📃 Documentation**
<details>
<summary>Bump webpack from 5.69.1 to 5.76.1 in /website <a href="https://github.com/ynput/OpenPype/pull/4624">#4624</a></summary>
Bumps [webpack](https://github.com/webpack/webpack) from 5.69.1 to 5.76.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/webpack/webpack/releases">webpack's releases</a>.</em></p>
<blockquote>
<h2>v5.76.1</h2>
<h2>Fixed</h2>
<ul>
<li>Added <code>assert/strict</code> built-in to <code>NodeTargetPlugin</code></li>
</ul>
<h2>Revert</h2>
<ul>
<li>Improve performance of <code>hashRegExp</code> lookup by <a href="https://github.com/ryanwilsonperkin"><code>@ryanwilsonperkin</code></a> in <a href="https://redirect.github.com/webpack/webpack/pull/16759">webpack/webpack#16759</a></li>
</ul>
<h2>v5.76.0</h2>
<h2>Bugfixes</h2>
<ul>
<li>Avoid cross-realm object access by <a href="https://github.com/Jack-Works"><code>@Jack-Works</code></a> in <a href="https://redirect.github.com/webpack/webpack/pull/16500">webpack/webpack#16500</a></li>
<li>Improve hash performance via conditional initialization by <a href="https://github.com/lvivski"><code>@lvivski</code></a> in <a href="https://redirect.github.com/webpack/webpack/pull/16491">webpack/webpack#16491</a></li>
<li>Serialize <code>generatedCode</code> info to fix bug in asset module cache restoration by <a href="https://github.com/ryanwilsonperkin"><code>@ryanwilsonperkin</code></a> in <a href="https://redirect.github.com/webpack/webpack/pull/16703">webpack/webpack#16703</a></li>
<li>Improve performance of <code>hashRegExp</code> lookup by <a href="https://github.com/ryanwilsonperkin"><code>@ryanwilsonperkin</code></a> in <a href="https://redirect.github.com/webpack/webpack/pull/16759">webpack/webpack#16759</a></li>
</ul>
<h2>Features</h2>
<ul>
<li>add <code>target</code> to <code>LoaderContext</code> type by <a href="https://github.com/askoufis"><code>@askoufis</code></a> in <a href="https://redirect.github.com/webpack/webpack/pull/16781">webpack/webpack#16781</a></li>
</ul>
<h2>Security</h2>
<ul>
<li><a href="https://github.com/advisories/GHSA-3rfm-jhwj-7488">CVE-2022-37603</a> fixed by <a href="https://github.com/akhilgkrishnan"><code>@akhilgkrishnan</code></a> in <a href="https://redirect.github.com/webpack/webpack/pull/16446">webpack/webpack#16446</a></li>
</ul>
<h2>Repo Changes</h2>
<ul>
<li>Fix HTML5 logo in README by <a href="https://github.com/jakebailey"><code>@jakebailey</code></a> in <a href="https://redirect.github.com/webpack/webpack/pull/16614">webpack/webpack#16614</a></li>
<li>Replace TypeScript logo in README by <a href="https://github.com/jakebailey"><code>@jakebailey</code></a> in <a href="https://redirect.github.com/webpack/webpack/pull/16613">webpack/webpack#16613</a></li>
<li>Update actions/cache dependencies by <a href="https://github.com/piwysocki"><code>@piwysocki</code></a> in <a href="https://redirect.github.com/webpack/webpack/pull/16493">webpack/webpack#16493</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/Jack-Works"><code>@Jack-Works</code></a> made their first contribution in <a href="https://redirect.github.com/webpack/webpack/pull/16500">webpack/webpack#16500</a></li>
<li><a href="https://github.com/lvivski"><code>@lvivski</code></a> made their first contribution in <a href="https://redirect.github.com/webpack/webpack/pull/16491">webpack/webpack#16491</a></li>
<li><a href="https://github.com/jakebailey"><code>@jakebailey</code></a> made their first contribution in <a href="https://redirect.github.com/webpack/webpack/pull/16614">webpack/webpack#16614</a></li>
<li><a href="https://github.com/akhilgkrishnan"><code>@akhilgkrishnan</code></a> made their first contribution in <a href="https://redirect.github.com/webpack/webpack/pull/16446">webpack/webpack#16446</a></li>
<li><a href="https://github.com/ryanwilsonperkin"><code>@ryanwilsonperkin</code></a> made their first contribution in <a href="https://redirect.github.com/webpack/webpack/pull/16703">webpack/webpack#16703</a></li>
<li><a href="https://github.com/piwysocki"><code>@piwysocki</code></a> made their first contribution in <a href="https://redirect.github.com/webpack/webpack/pull/16493">webpack/webpack#16493</a></li>
<li><a href="https://github.com/askoufis"><code>@askoufis</code></a> made their first contribution in <a href="https://redirect.github.com/webpack/webpack/pull/16781">webpack/webpack#16781</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/webpack/webpack/compare/v5.75.0...v5.76.0">https://github.com/webpack/webpack/compare/v5.75.0...v5.76.0</a></p>
<h2>v5.75.0</h2>
<h1>Bugfixes</h1>
<ul>
<li><code>experiments.*</code> normalize to <code>false</code> when opt-out</li>
<li>avoid <code>NaN%</code></li>
<li>show the correct error when using a conflicting chunk name in code</li>
<li>HMR code tests existance of <code>window</code> before trying to access it</li>
<li>fix <code>eval-nosources-*</code> actually exclude sources</li>
<li>fix race condition where no module is returned from processing module</li>
<li>fix position of standalong semicolon in runtime code</li>
</ul>
<h1>Features</h1>
<ul>
<li>add support for <code>@import</code> to extenal CSS when using experimental CSS in node</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/webpack/webpack/commit/21be52b681c477f8ebc41c1b0e7a7a8ac4fa7008"><code>21be52b</code></a> Merge pull request <a href="https://redirect.github.com/webpack/webpack/issues/16804">#16804</a> from webpack/chore-patch-release</li>
<li><a href="https://github.com/webpack/webpack/commit/1cce945dd6c3576d37d3940a0233fd087ce3f6ff"><code>1cce945</code></a> chore(release): 5.76.1</li>
<li><a href="https://github.com/webpack/webpack/commit/e76ad9e724410f10209caa2ba86875ca8cf5ed61"><code>e76ad9e</code></a> Merge pull request <a href="https://redirect.github.com/webpack/webpack/issues/16803">#16803</a> from ryanwilsonperkin/revert-16759-real-content-has...</li>
<li><a href="https://github.com/webpack/webpack/commit/52b1b0e4ada7c11e7f1b4f3d69b50684938c684e"><code>52b1b0e</code></a> Revert &quot;Improve performance of hashRegExp lookup&quot;</li>
<li><a href="https://github.com/webpack/webpack/commit/c989143379d344543e4161fec60f3a21beb9e3ce"><code>c989143</code></a> Merge pull request <a href="https://redirect.github.com/webpack/webpack/issues/16766">#16766</a> from piranna/patch-1</li>
<li><a href="https://github.com/webpack/webpack/commit/710eaf4ddaea505e040a24beeb45a769f9e3761b"><code>710eaf4</code></a> Merge pull request <a href="https://redirect.github.com/webpack/webpack/issues/16789">#16789</a> from dmichon-msft/contenthash-hashsalt</li>
<li><a href="https://github.com/webpack/webpack/commit/5d6446822aff579a5d3d9503ec2a16437d2f71d1"><code>5d64468</code></a> Merge pull request <a href="https://redirect.github.com/webpack/webpack/issues/16792">#16792</a> from webpack/update-version</li>
<li><a href="https://github.com/webpack/webpack/commit/67af5ec1f05fb7cf06be6acf27353aef105ddcbc"><code>67af5ec</code></a> chore(release): 5.76.0</li>
<li><a href="https://github.com/webpack/webpack/commit/97b1718720c33f1b17302a74c5284b01e02ec001"><code>97b1718</code></a> Merge pull request <a href="https://redirect.github.com/webpack/webpack/issues/16781">#16781</a> from askoufis/loader-context-target-type</li>
<li><a href="https://github.com/webpack/webpack/commit/b84efe6224b276bf72e4c5e2f4e76acddfaeef07"><code>b84efe6</code></a> Merge pull request <a href="https://redirect.github.com/webpack/webpack/issues/16759">#16759</a> from ryanwilsonperkin/real-content-hash-regex-perf</li>
<li>Additional commits viewable in <a href="https://github.com/webpack/webpack/compare/v5.69.1...v5.76.1">compare view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a href="https://www.npmjs.com/~evilebottnawi">evilebottnawi</a>, a new releaser for webpack since your current version.</p>
</details>
<br />
[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=webpack&package-manager=npm_and_yarn&previous-version=5.69.1&new-version=5.76.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language
- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language
- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language
- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/ynput/OpenPype/network/alerts).
</details>
___
</details>
<details>
<summary>Documentation: Add Extract Burnin documentation <a href="https://github.com/ynput/OpenPype/pull/4765">#4765</a></summary>
Add documentation for Extract Burnin global plugin settings.
___
</details>
<details>
<summary>Documentation: Move publisher related tips to publisher area <a href="https://github.com/ynput/OpenPype/pull/4772">#4772</a></summary>
Move publisher related tips for After Effects artist documentation to the correct position.
___
</details>
<details>
<summary>Documentation: Add extra terminology to the key concepts glossary <a href="https://github.com/ynput/OpenPype/pull/4838">#4838</a></summary>
Tweak some of the key concepts in the documentation.
___
</details>
### **Merged pull requests**
<details>
<summary>Maya: Refactor Extract Look with dedicated processors for maketx <a href="https://github.com/ynput/OpenPype/pull/4711">#4711</a></summary>
Refactor Maya extract look to fix some issues:
- [x] Allow Extraction with maketx with OCIO Color Management enabled in Maya.
- [x] Fix file hashing so it includes arguments to maketx, so that when arguments change it correctly generates a new hash
- [x] Fix maketx destination colorspace when OCIO is enabled
- [x] Use pre-collected colorspaces of the resources instead of trying to retrieve again in Extract Look
- [x] Fix colorspace attributes being reinterpreted by maya on export (fix remapping) - goal is to resolve #2337
- [x] Fix support for checking config path of maya default OCIO config (due to using `lib.get_color_management_preferences` which remaps that path)
- [x] Merged in #2971 to refactor MakeTX into TextureProcessor and also support generating Redshift `.rstexbin` files. - goal is to resolve #2599
- [x] Allow custom arguments to `maketx` from OpenPype Settings like mentioned here by @fabiaserra for arguments like: `--monochrome-detect`, `--opaque-detect`, `--checknan`.
- [x] Actually fix the code and make it work. :) (I'll try to keep below checkboxes in sync with my code changes)
- [x] Publishing without texture processor should work (no maketx + no rstexbin)
- [x] Publishing with maketx should work
- [x] Publishing with rstexbin should work
- [x] Test it. (This is just me doing some test-runs, please still test the PR!)
___
</details>
<details>
<summary>Maya template builder load all assets linked to the shot <a href="https://github.com/ynput/OpenPype/pull/4761">#4761</a></summary>
Problem
All the assets of the ftrack project are loaded and not those linked to the shot
How get error
Open maya in the context of shot, then build a new scene with the "Build Workfile from template" button in "OpenPype" menu.
![image](https://user-images.githubusercontent.com/7068597/229124652-573a23d7-a2b2-4d50-81bf-7592c00d24dc.png)
___
</details>
<details>
<summary>Global: Do not force instance data with frame ranges of the asset <a href="https://github.com/ynput/OpenPype/pull/4383">#4383</a></summary>
<strong>This aims to resolve #4317
</strong>
___
</details>
<details>
<summary>Cosmetics: Fix some grammar in docstrings and messages (and some code) <a href="https://github.com/ynput/OpenPype/pull/4752">#4752</a></summary>
Tweak some grammar in codebase
___
</details>
<details>
<summary>Deadline: Submit publish job fails due root work hardcode - OP-5528 <a href="https://github.com/ynput/OpenPype/pull/4775">#4775</a></summary>
Generating config templates was hardcoded to `root[work]`. This PR fixes that.
___
</details>
<details>
<summary>CreateContext: Added option to remove Unknown attributes <a href="https://github.com/ynput/OpenPype/pull/4776">#4776</a></summary>
Added option to remove attributes with UnkownAttrDef on instances. Pop of key will also remove the attribute definition from attribute values, so they're not recreated again.
___
</details>
## [3.15.3](https://github.com/ynput/OpenPype/tree/3.15.3)

View file

@ -1216,7 +1216,7 @@ def get_representations(
version_ids=version_ids,
context_filters=context_filters,
names_by_version_ids=names_by_version_ids,
standard=True,
standard=standard,
archived=archived,
fields=fields
)

View file

@ -42,13 +42,5 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook):
self.log.info("Current context does not have any workfile yet.")
return
# Determine whether to open workfile post initialization.
if self.host_name == "maya":
key = "open_workfile_post_initialization"
if self.data["project_settings"]["maya"][key]:
self.log.debug("Opening workfile post initialization.")
self.data["env"]["OPENPYPE_" + key.upper()] = "1"
return
# Add path to workfile to arguments
self.launch_context.launch_args.append(last_workfile)

View file

@ -53,10 +53,10 @@ class CollectWorkfile(pyblish.api.ContextPlugin):
"active": True,
"asset": asset_entity["name"],
"task": task,
"frameStart": asset_entity["data"]["frameStart"],
"frameEnd": asset_entity["data"]["frameEnd"],
"handleStart": asset_entity["data"]["handleStart"],
"handleEnd": asset_entity["data"]["handleEnd"],
"frameStart": context.data['frameStart'],
"frameEnd": context.data['frameEnd'],
"handleStart": context.data['handleStart'],
"handleEnd": context.data['handleEnd'],
"fps": asset_entity["data"]["fps"],
"resolutionWidth": asset_entity["data"].get(
"resolutionWidth",

View file

@ -0,0 +1,185 @@
"""Library to register OpenPype Creators for Houdini TAB node search menu.
This can be used to install custom houdini tools for the TAB search
menu which will trigger a publish instance to be created interactively.
The Creators are automatically registered on launch of Houdini through the
Houdini integration's `host.install()` method.
"""
import contextlib
import tempfile
import logging
import os
from openpype.pipeline import registered_host
from openpype.pipeline.create import CreateContext
from openpype.resources import get_openpype_icon_filepath
import hou
log = logging.getLogger(__name__)
CREATE_SCRIPT = """
from openpype.hosts.houdini.api.creator_node_shelves import create_interactive
create_interactive("{identifier}")
"""
def create_interactive(creator_identifier):
"""Create a Creator using its identifier interactively.
This is used by the generated shelf tools as callback when a user selects
the creator from the node tab search menu.
Args:
creator_identifier (str): The creator identifier of the Creator plugin
to create.
Return:
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)
before = context.instances_by_id.copy()
# Create the instance
context.create(
creator_identifier=creator_identifier,
variant=variant,
pre_create_data={"use_selection": True}
)
# For convenience we set the new node as current since that's much more
# familiar to the artist when creating a node interactively
# TODO Allow to disable auto-select in studio settings or user preferences
after = context.instances_by_id
new = set(after) - set(before)
if new:
# Select the new instance
for instance_id in new:
instance = after[instance_id]
node = hou.node(instance.get("instance_node"))
node.setCurrent(True)
return list(new)
@contextlib.contextmanager
def shelves_change_block():
"""Write shelf changes at the end of the context."""
hou.shelves.beginChangeBlock()
try:
yield
finally:
hou.shelves.endChangeBlock()
def install():
"""Install the Creator plug-ins to show in Houdini's TAB node search menu.
This function is re-entrant and can be called again to reinstall and
update the node definitions. For example during development it can be
useful to call it manually:
>>> from openpype.hosts.houdini.api.creator_node_shelves import install
>>> install()
Returns:
list: List of `hou.Tool` instances
"""
host = registered_host()
# Store the filepath on the host
# TODO: Define a less hacky static shelf path for current houdini session
filepath_attr = "_creator_node_shelf_filepath"
filepath = getattr(host, filepath_attr, None)
if filepath is None:
f = tempfile.NamedTemporaryFile(prefix="houdini_creator_nodes_",
suffix=".shelf",
delete=False)
f.close()
filepath = f.name
setattr(host, filepath_attr, filepath)
elif os.path.exists(filepath):
# Remove any existing shelf file so that we can completey regenerate
# and update the tools file if creator identifiers change
os.remove(filepath)
icon = get_openpype_icon_filepath()
# Create context only to get creator plugins, so we don't reset and only
# populate what we need to retrieve the list of creator plugins
create_context = CreateContext(host, reset=False)
create_context.reset_current_context()
create_context._reset_creator_plugins()
log.debug("Writing OpenPype Creator nodes to shelf: {}".format(filepath))
tools = []
with shelves_change_block():
for identifier, creator in create_context.manual_creators.items():
# TODO: Allow the creator plug-in itself to override the categories
# for where they are shown, by e.g. defining
# `Creator.get_network_categories()`
key = "openpype_create.{}".format(identifier)
log.debug(f"Registering {key}")
script = CREATE_SCRIPT.format(identifier=identifier)
data = {
"script": script,
"language": hou.scriptLanguage.Python,
"icon": icon,
"help": "Create OpenPype publish instance for {}".format(
creator.label
),
"help_url": None,
"network_categories": [
hou.ropNodeTypeCategory(),
hou.sopNodeTypeCategory()
],
"viewer_categories": [],
"cop_viewer_categories": [],
"network_op_type": None,
"viewer_op_type": None,
"locations": ["OpenPype"]
}
label = "Create {}".format(creator.label)
tool = hou.shelves.tool(key)
if tool:
tool.setData(**data)
tool.setLabel(label)
else:
tool = hou.shelves.newTool(
file_path=filepath,
name=key,
label=label,
**data
)
tools.append(tool)
# Ensure the shelf is reloaded
hou.shelves.loadFile(filepath)
return tools

View file

@ -127,6 +127,8 @@ def get_output_parameter(node):
return node.parm("filename")
elif node_type == "comp":
return node.parm("copoutput")
elif node_type == "opengl":
return node.parm("picture")
elif node_type == "arnold":
if node.evalParm("ar_ass_export_enable"):
return node.parm("ar_ass_file")

View file

@ -18,7 +18,7 @@ from openpype.pipeline import (
)
from openpype.pipeline.load import any_outdated_containers
from openpype.hosts.houdini import HOUDINI_HOST_DIR
from openpype.hosts.houdini.api import lib, shelves
from openpype.hosts.houdini.api import lib, shelves, creator_node_shelves
from openpype.lib import (
register_event_callback,
@ -83,6 +83,10 @@ class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
_set_context_settings()
shelves.generate_shelves()
if not IS_HEADLESS:
import hdefereval # noqa, hdefereval is only available in ui mode
hdefereval.executeDeferred(creator_node_shelves.install)
def has_unsaved_changes(self):
return hou.hipFile.hasUnsavedChanges()

View file

@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating openGL reviews."""
from openpype.hosts.houdini.api import plugin
from openpype.lib import EnumDef, BoolDef, NumberDef
class CreateReview(plugin.HoudiniCreator):
"""Review with OpenGL ROP"""
identifier = "io.openpype.creators.houdini.review"
label = "Review"
family = "review"
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"})
instance_data["imageFormat"] = pre_create_data.get("imageFormat")
instance_data["keepImages"] = pre_create_data.get("keepImages")
instance = super(CreateReview, self).create(
subset_name,
instance_data,
pre_create_data)
instance_node = hou.node(instance.get("instance_node"))
frame_range = hou.playbar.frameRange()
filepath = "{root}/{subset}/{subset}.$F4.{ext}".format(
root=hou.text.expandString("$HIP/pyblish"),
subset="`chs(\"subset\")`", # keep dynamic link to subset
ext=pre_create_data.get("image_format") or "png"
)
parms = {
"picture": filepath,
"trange": 1,
# Unlike many other ROP nodes the opengl node does not default
# to expression of $FSTART and $FEND so we preserve that behavior
# but do set the range to the frame range of the playbar
"f1": frame_range[0],
"f2": frame_range[1],
}
override_resolution = pre_create_data.get("override_resolution")
if override_resolution:
parms.update({
"tres": override_resolution,
"res1": pre_create_data.get("resx"),
"res2": pre_create_data.get("resy"),
"aspect": pre_create_data.get("aspect"),
})
if self.selected_nodes:
# The first camera found in selection we will use as camera
# Other node types we set in force objects
camera = None
force_objects = []
for node in self.selected_nodes:
path = node.path()
if node.type().name() == "cam":
if camera:
continue
camera = path
else:
force_objects.append(path)
if not camera:
self.log.warning("No camera found in selection.")
parms.update({
"camera": camera or "",
"scenepath": "/obj",
"forceobjects": " ".join(force_objects),
"vobjects": "" # clear candidate objects from '*' value
})
instance_node.setParms(parms)
to_lock = ["id", "family"]
self.lock_parameters(instance_node, to_lock)
def get_pre_create_attr_defs(self):
attrs = super(CreateReview, self).get_pre_create_attr_defs()
image_format_enum = [
"bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png",
"rad", "rat", "rta", "sgi", "tga", "tif",
]
return attrs + [
BoolDef("keepImages",
label="Keep Image Sequences",
default=False),
EnumDef("imageFormat",
image_format_enum,
default="png",
label="Image Format Options"),
BoolDef("override_resolution",
label="Override resolution",
tooltip="When disabled the resolution set on the camera "
"is used instead.",
default=True),
NumberDef("resx",
label="Resolution Width",
default=1280,
minimum=2,
decimals=0),
NumberDef("resy",
label="Resolution Height",
default=720,
minimum=2,
decimals=0),
NumberDef("aspect",
label="Aspect Ratio",
default=1.0,
minimum=0.0001,
decimals=3)
]

View file

@ -14,7 +14,7 @@ class CollectFrames(pyblish.api.InstancePlugin):
order = pyblish.api.CollectorOrder
label = "Collect Frames"
families = ["vdbcache", "imagesequence", "ass", "redshiftproxy"]
families = ["vdbcache", "imagesequence", "ass", "redshiftproxy", "review"]
def process(self, instance):

View file

@ -0,0 +1,52 @@
import hou
import pyblish.api
class CollectHoudiniReviewData(pyblish.api.InstancePlugin):
"""Collect Review Data."""
label = "Collect Review Data"
order = pyblish.api.CollectorOrder + 0.1
hosts = ["houdini"]
families = ["review"]
def process(self, instance):
# This fixes the burnin having the incorrect start/end timestamps
# because without this it would take it from the context instead
# which isn't the actual frame range that this instance renders.
instance.data["handleStart"] = 0
instance.data["handleEnd"] = 0
# Get the camera from the rop node to collect the focal length
ropnode_path = instance.data["instance_node"]
ropnode = hou.node(ropnode_path)
camera_path = ropnode.parm("camera").eval()
camera_node = hou.node(camera_path)
if not camera_node:
raise RuntimeError("No valid camera node found on review node: "
"{}".format(camera_path))
# Collect focal length.
focal_length_parm = camera_node.parm("focal")
if not focal_length_parm:
self.log.warning("No 'focal' (focal length) parameter found on "
"camera: {}".format(camera_path))
return
if focal_length_parm.isTimeDependent():
start = instance.data["frameStart"]
end = instance.data["frameEnd"] + 1
focal_length = [
focal_length_parm.evalAsFloatAtFrame(t)
for t in range(int(start), int(end))
]
else:
focal_length = focal_length_parm.evalAsFloat()
# Store focal length in `burninDataMembers`
burnin_members = instance.data.setdefault("burninDataMembers", {})
burnin_members["focalLength"] = focal_length
instance.data.setdefault("families", []).append('ftrack')

View file

@ -0,0 +1,58 @@
import os
import pyblish.api
from openpype.pipeline import (
publish,
OptionalPyblishPluginMixin
)
from openpype.hosts.houdini.api.lib import render_rop
import hou
class ExtractOpenGL(publish.Extractor,
OptionalPyblishPluginMixin):
order = pyblish.api.ExtractorOrder - 0.01
label = "Extract OpenGL"
families = ["review"]
hosts = ["houdini"]
optional = True
def process(self, instance):
if not self.is_active(instance.data):
return
ropnode = hou.node(instance.data.get("instance_node"))
output = ropnode.evalParm("picture")
staging_dir = os.path.normpath(os.path.dirname(output))
instance.data["stagingDir"] = staging_dir
file_name = os.path.basename(output)
self.log.info("Extracting '%s' to '%s'" % (file_name,
staging_dir))
render_rop(ropnode)
output = instance.data["frames"]
tags = ["review"]
if not instance.data.get("keepImages"):
tags.append("delete")
representation = {
"name": instance.data["imageFormat"],
"ext": instance.data["imageFormat"],
"files": output,
"stagingDir": staging_dir,
"frameStart": instance.data["frameStart"],
"frameEnd": instance.data["frameEnd"],
"tags": tags,
"preview": True,
"camera_name": instance.data.get("review_camera")
}
if "representations" not in instance.data:
instance.data["representations"] = []
instance.data["representations"].append(representation)

View file

@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
import pyblish.api
from openpype.pipeline import PublishValidationError
import hou
class ValidateSceneReview(pyblish.api.InstancePlugin):
"""Validator Some Scene Settings before publishing the review
1. Scene Path
2. Resolution
"""
order = pyblish.api.ValidatorOrder
families = ["review"]
hosts = ["houdini"]
label = "Scene Setting for review"
def process(self, instance):
invalid = self.get_invalid_scene_path(instance)
report = []
if invalid:
report.append(
"Scene path does not exist: '%s'" % invalid[0],
)
invalid = self.get_invalid_resolution(instance)
if invalid:
report.extend(invalid)
if report:
raise PublishValidationError(
"\n\n".join(report),
title=self.label)
def get_invalid_scene_path(self, instance):
node = hou.node(instance.data.get("instance_node"))
scene_path_parm = node.parm("scenepath")
scene_path_node = scene_path_parm.evalAsNode()
if not scene_path_node:
return [scene_path_parm.evalAsString()]
def get_invalid_resolution(self, instance):
node = hou.node(instance.data.get("instance_node"))
# The resolution setting is only used when Override Camera Resolution
# is enabled. So we skip validation if it is disabled.
override = node.parm("tres").eval()
if not override:
return
invalid = []
res_width = node.parm("res1").eval()
res_height = node.parm("res2").eval()
if res_width == 0:
invalid.append("Override Resolution width is set to zero.")
if res_height == 0:
invalid.append("Override Resolution height is set to zero")
return invalid

View file

@ -62,6 +62,7 @@ class CollectRender(pyblish.api.InstancePlugin):
"frameStart": context.data['frameStart'],
"frameEnd": context.data['frameEnd'],
"version": version_int,
"farm": True
}
self.log.info("data: {0}".format(data))
instance.data.update(data)

View file

@ -32,7 +32,13 @@ from openpype.pipeline import (
load_container,
registered_host,
)
from openpype.pipeline.context_tools import get_current_project_asset
from openpype.pipeline.context_tools import (
get_current_asset_name,
get_current_project_asset,
get_current_project_name,
get_current_task_name
)
from openpype.lib.profiles_filtering import filter_profiles
self = sys.modules[__name__]
@ -112,6 +118,18 @@ FLOAT_FPS = {23.98, 23.976, 29.97, 47.952, 59.94}
RENDERLIKE_INSTANCE_FAMILIES = ["rendering", "vrayscene"]
DISPLAY_LIGHTS_VALUES = [
"project_settings", "default", "all", "selected", "flat", "none"
]
DISPLAY_LIGHTS_LABELS = [
"Use Project Settings",
"Default Lighting",
"All Lights",
"Selected Lights",
"Flat Lighting",
"No Lights"
]
def get_main_window():
"""Acquire Maya's main window"""
@ -292,15 +310,20 @@ def collect_animation_data(fps=False):
"""
# get scene values as defaults
start = cmds.playbackOptions(query=True, animationStartTime=True)
end = cmds.playbackOptions(query=True, animationEndTime=True)
frame_start = cmds.playbackOptions(query=True, minTime=True)
frame_end = cmds.playbackOptions(query=True, maxTime=True)
handle_start = cmds.playbackOptions(query=True, animationStartTime=True)
handle_end = cmds.playbackOptions(query=True, animationEndTime=True)
handle_start = frame_start - handle_start
handle_end = handle_end - frame_end
# build attributes
data = OrderedDict()
data["frameStart"] = start
data["frameEnd"] = end
data["handleStart"] = 0
data["handleEnd"] = 0
data["frameStart"] = frame_start
data["frameEnd"] = frame_end
data["handleStart"] = handle_start
data["handleEnd"] = handle_end
data["step"] = 1.0
if fps:
@ -2134,9 +2157,13 @@ def get_frame_range():
"""Get the current assets frame range and handles."""
# Set frame start/end
project_name = legacy_io.active_project()
asset_name = legacy_io.Session["AVALON_ASSET"]
project_name = get_current_project_name()
task_name = get_current_task_name()
asset_name = get_current_asset_name()
asset = get_asset_by_name(project_name, asset_name)
settings = get_project_settings(project_name)
include_handles_settings = settings["maya"]["include_handles"]
current_task = asset.get("data").get("tasks").get(task_name)
frame_start = asset["data"].get("frameStart")
frame_end = asset["data"].get("frameEnd")
@ -2148,6 +2175,26 @@ def get_frame_range():
handle_start = asset["data"].get("handleStart") or 0
handle_end = asset["data"].get("handleEnd") or 0
animation_start = frame_start
animation_end = frame_end
include_handles = include_handles_settings["include_handles_default"]
for item in include_handles_settings["per_task_type"]:
if current_task["type"] in item["task_type"]:
include_handles = item["include_handles"]
break
if include_handles:
animation_start -= int(handle_start)
animation_end += int(handle_end)
cmds.playbackOptions(
minTime=frame_start,
maxTime=frame_end,
animationStartTime=animation_start,
animationEndTime=animation_end
)
cmds.currentTime(frame_start)
return {
"frameStart": frame_start,
"frameEnd": frame_end,
@ -2166,7 +2213,6 @@ def reset_frame_range(playback=True, render=True, fps=True):
Defaults to True.
fps (bool, Optional): Whether to set scene FPS. Defaults to True.
"""
if fps:
fps = convert_to_maya_fps(
float(legacy_io.Session.get("AVALON_FPS", 25))
@ -3655,7 +3701,17 @@ def get_color_management_preferences():
# Split view and display from view_transform. view_transform comes in
# format of "{view} ({display})".
regex = re.compile(r"^(?P<view>.+) \((?P<display>.+)\)$")
if int(cmds.about(version=True)) <= 2020:
# view_transform comes in format of "{view} {display}" in 2020.
regex = re.compile(r"^(?P<view>.+) (?P<display>.+)$")
match = regex.match(data["view_transform"])
if not match:
raise ValueError(
"Unable to parse view and display from Maya view transform: '{}' "
"using regex '{}'".format(data["view_transform"], regex.pattern)
)
data.update({
"display": match.group("display"),
"view": match.group("view")
@ -3812,3 +3868,48 @@ def get_all_children(nodes):
iterator.next() # noqa: B305
return list(traversed)
def get_capture_preset(task_name, task_type, subset, project_settings, log):
"""Get capture preset for playblasting.
Logic for transitioning from old style capture preset to new capture preset
profiles.
Args:
task_name (str): Task name.
take_type (str): Task type.
subset (str): Subset name.
project_settings (dict): Project settings.
log (object): Logging object.
"""
capture_preset = None
filtering_criteria = {
"hosts": "maya",
"families": "review",
"task_names": task_name,
"task_types": task_type,
"subset": subset
}
plugin_settings = project_settings["maya"]["publish"]["ExtractPlayblast"]
if plugin_settings["profiles"]:
profile = filter_profiles(
plugin_settings["profiles"],
filtering_criteria,
logger=log
)
capture_preset = profile.get("capture_preset")
else:
log.warning("No profiles present for Extract Playblast")
# Backward compatibility for deprecated Extract Playblast settings
# without profiles.
if capture_preset is None:
log.debug(
"Falling back to deprecated Extract Playblast capture preset "
"because no new style playblast profiles are defined."
)
capture_preset = plugin_settings["capture_preset"]
return capture_preset or {}

View file

@ -1,4 +1,5 @@
import os
import re
from maya import cmds
@ -12,6 +13,7 @@ from openpype.pipeline import (
AVALON_CONTAINER_ID,
Anatomy,
)
from openpype.pipeline.load import LoadError
from openpype.settings import get_project_settings
from .pipeline import containerise
from . import lib
@ -82,6 +84,44 @@ def get_reference_node_parents(ref):
return parents
def get_custom_namespace(custom_namespace):
"""Return unique namespace.
The input namespace can contain a single group
of '#' number tokens to indicate where the namespace's
unique index should go. The amount of tokens defines
the zero padding of the number, e.g ### turns into 001.
Warning: Note that a namespace will always be
prefixed with a _ if it starts with a digit
Example:
>>> get_custom_namespace("myspace_##_")
# myspace_01_
>>> get_custom_namespace("##_myspace")
# _01_myspace
>>> get_custom_namespace("myspace##")
# myspace01
"""
split = re.split("([#]+)", custom_namespace, 1)
if len(split) == 3:
base, padding, suffix = split
padding = "%0{}d".format(len(padding))
else:
base = split[0]
padding = "%02d" # default padding
suffix = ""
return lib.unique_namespace(
base,
format=padding,
prefix="_" if not base or base[0].isdigit() else "",
suffix=suffix
)
class Creator(LegacyCreator):
defaults = ['Main']
@ -143,15 +183,46 @@ class ReferenceLoader(Loader):
assert os.path.exists(self.fname), "%s does not exist." % self.fname
asset = context['asset']
subset = context['subset']
settings = get_project_settings(context['project']['name'])
custom_naming = settings['maya']['load']['reference_loader']
loaded_containers = []
count = options.get("count") or 1
for c in range(0, count):
namespace = namespace or lib.unique_namespace(
"{}_{}_".format(asset["name"], context["subset"]["name"]),
prefix="_" if asset["name"][0].isdigit() else "",
suffix="_",
if not custom_naming['namespace']:
raise LoadError("No namespace specified in "
"Maya ReferenceLoader settings")
elif not custom_naming['group_name']:
raise LoadError("No group name specified in "
"Maya ReferenceLoader settings")
formatting_data = {
"asset_name": asset['name'],
"asset_type": asset['type'],
"subset": subset['name'],
"family": (
subset['data'].get('family') or
subset['data']['families'][0]
)
}
custom_namespace = custom_naming['namespace'].format(
**formatting_data
)
custom_group_name = custom_naming['group_name'].format(
**formatting_data
)
count = options.get("count") or 1
for c in range(0, count):
namespace = get_custom_namespace(custom_namespace)
group_name = "{}:{}".format(
namespace,
custom_group_name
)
options['group_name'] = group_name
# Offset loaded subset
if "offset" in options:
@ -187,7 +258,7 @@ class ReferenceLoader(Loader):
return loaded_containers
def process_reference(self, context, name, namespace, data):
def process_reference(self, context, name, namespace, options):
"""To be implemented by subclass"""
raise NotImplementedError("Must be implemented by subclass")

View file

@ -234,26 +234,10 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin):
return self.get_load_plugin_options(options)
def cleanup_placeholder(self, placeholder, failed):
"""Hide placeholder, parent them to root
add them to placeholder set and register placeholder's parent
to keep placeholder info available for future use
"""Hide placeholder, add them to placeholder set
"""
node = placeholder._scene_identifier
node_parent = placeholder.data["parent"]
if node_parent:
cmds.setAttr(node + ".parent", node_parent, type="string")
if cmds.getAttr(node + ".index") < 0:
cmds.setAttr(node + ".index", placeholder.data["index"])
holding_sets = cmds.listSets(object=node)
if holding_sets:
for set in holding_sets:
cmds.sets(node, remove=set)
if cmds.listRelatives(node, p=True):
node = cmds.parent(node, world=True)[0]
cmds.sets(node, addElement=PLACEHOLDER_SET)
cmds.hide(node)
cmds.setAttr(node + ".hiddenInOutliner", True)
@ -286,8 +270,6 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin):
elif not cmds.sets(root, q=True):
return
if placeholder.data["parent"]:
cmds.parent(nodes_to_parent, placeholder.data["parent"])
# Move loaded nodes to correct index in outliner hierarchy
placeholder_form = cmds.xform(
placeholder.scene_identifier,

View file

@ -0,0 +1,29 @@
from openpype.lib import PreLaunchHook
class MayaPreAutoLoadPlugins(PreLaunchHook):
"""Define -noAutoloadPlugins command flag."""
# Before AddLastWorkfileToLaunchArgs
order = 9
app_groups = ["maya"]
def execute(self):
# Ignore if there's no last workfile to start.
if not self.data.get("start_last_workfile"):
return
maya_settings = self.data["project_settings"]["maya"]
enabled = maya_settings["explicit_plugins_loading"]["enabled"]
if enabled:
# Force disable the `AddLastWorkfileToLaunchArgs`.
self.data.pop("start_last_workfile")
# Force post initialization so our dedicated plug-in load can run
# prior to Maya opening a scene file.
key = "OPENPYPE_OPEN_WORKFILE_POST_INITIALIZATION"
self.launch_context.env[key] = "1"
self.log.debug("Explicit plugins loading.")
self.launch_context.launch_args.append("-noAutoloadPlugins")

View file

@ -0,0 +1,25 @@
from openpype.lib import PreLaunchHook
class MayaPreOpenWorkfilePostInitialization(PreLaunchHook):
"""Define whether open last workfile should run post initialize."""
# Before AddLastWorkfileToLaunchArgs.
order = 9
app_groups = ["maya"]
def execute(self):
# Ignore if there's no last workfile to start.
if not self.data.get("start_last_workfile"):
return
maya_settings = self.data["project_settings"]["maya"]
enabled = maya_settings["open_workfile_post_initialization"]
if enabled:
# Force disable the `AddLastWorkfileToLaunchArgs`.
self.data.pop("start_last_workfile")
self.log.debug("Opening workfile post initialization.")
key = "OPENPYPE_OPEN_WORKFILE_POST_INITIALIZATION"
self.launch_context.env[key] = "1"

View file

@ -1,8 +1,14 @@
import os
from collections import OrderedDict
import json
from openpype.hosts.maya.api import (
lib,
plugin
)
from openpype.settings import get_project_settings
from openpype.pipeline import get_current_project_name, get_current_task_name
from openpype.client import get_asset_by_name
class CreateReview(plugin.Creator):
@ -32,6 +38,23 @@ class CreateReview(plugin.Creator):
super(CreateReview, self).__init__(*args, **kwargs)
data = OrderedDict(**self.data)
project_name = get_current_project_name()
asset_doc = get_asset_by_name(project_name, data["asset"])
task_name = get_current_task_name()
preset = lib.get_capture_preset(
task_name,
asset_doc["data"]["tasks"][task_name]["type"],
data["subset"],
get_project_settings(project_name),
self.log
)
if os.environ.get("OPENPYPE_DEBUG") == "1":
self.log.debug(
"Using preset: {}".format(
json.dumps(preset, indent=4, sort_keys=True)
)
)
# Option for using Maya or asset frame range in settings.
frame_range = lib.get_frame_range()
if self.useMayaTimeline:
@ -40,12 +63,14 @@ class CreateReview(plugin.Creator):
data[key] = value
data["fps"] = lib.collect_animation_data(fps=True)["fps"]
data["review_width"] = self.Width
data["review_height"] = self.Height
data["isolate"] = self.isolate
data["keepImages"] = self.keepImages
data["imagePlane"] = self.imagePlane
data["transparency"] = self.transparency
data["panZoom"] = self.panZoom
data["review_width"] = preset["Resolution"]["width"]
data["review_height"] = preset["Resolution"]["height"]
data["isolate"] = preset["Generic"]["isolate_view"]
data["imagePlane"] = preset["Viewport Options"]["imagePlane"]
data["panZoom"] = preset["Generic"]["pan_zoom"]
data["displayLights"] = lib.DISPLAY_LIGHTS_LABELS
self.data = data

View file

@ -14,7 +14,7 @@ class AbcLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
icon = "code-fork"
color = "orange"
def process_reference(self, context, name, namespace, data):
def process_reference(self, context, name, namespace, options):
import maya.cmds as cmds
from openpype.hosts.maya.api.lib import unique_namespace
@ -41,7 +41,7 @@ class AbcLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
namespace=namespace,
sharedReferenceFile=False,
groupReference=True,
groupName="{}:{}".format(namespace, name),
groupName=options['group_name'],
reference=True,
returnNewNodes=True)

View file

@ -125,14 +125,15 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
except ValueError:
family = "model"
group_name = "{}:_GRP".format(namespace)
# True by default to keep legacy behaviours
attach_to_root = options.get("attach_to_root", True)
group_name = options["group_name"]
with maintained_selection():
cmds.loadPlugin("AbcImport.mll", quiet=True)
file_url = self.prepare_root_value(self.fname,
context["project"]["name"])
nodes = cmds.file(file_url,
namespace=namespace,
sharedReferenceFile=False,

View file

@ -19,8 +19,7 @@ class YetiRigLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
def process_reference(
self, context, name=None, namespace=None, options=None
):
group_name = "{}:{}".format(namespace, name)
group_name = options['group_name']
with lib.maintained_selection():
file_url = self.prepare_root_value(
self.fname, context["project"]["name"]

View file

@ -4,7 +4,7 @@ import pyblish.api
from openpype.client import get_subset_by_name
from openpype.pipeline import legacy_io, KnownPublishError
from openpype.hosts.maya.api.lib import get_attribute_input
from openpype.hosts.maya.api import lib
class CollectReview(pyblish.api.InstancePlugin):
@ -29,22 +29,9 @@ class CollectReview(pyblish.api.InstancePlugin):
# get cameras
members = instance.data['setMembers']
cameras = cmds.ls(members, long=True,
dag=True, cameras=True)
self.log.debug('members: {}'.format(members))
# validate required settings
if len(cameras) == 0:
raise KnownPublishError("No camera found in review "
"instance: {}".format(instance))
elif len(cameras) > 2:
raise KnownPublishError(
"Only a single camera is allowed for a review instance but "
"more than one camera found in review instance: {}. "
"Cameras found: {}".format(instance, ", ".join(cameras)))
camera = cameras[0]
self.log.debug('camera: {}'.format(camera))
cameras = cmds.ls(members, long=True, dag=True, cameras=True)
camera = cameras[0] if cameras else None
context = instance.context
objectset = context.data['objectsets']
@ -75,6 +62,7 @@ class CollectReview(pyblish.api.InstancePlugin):
else:
data['families'] = ['review']
data["cameras"] = cameras
data['review_camera'] = camera
data['frameStartFtrack'] = instance.data["frameStartHandle"]
data['frameEndFtrack'] = instance.data["frameEndHandle"]
@ -109,6 +97,7 @@ class CollectReview(pyblish.api.InstancePlugin):
self.log.debug("Existing subsets found, keep legacy name.")
instance.data['subset'] = legacy_subset_name
instance.data["cameras"] = cameras
instance.data['review_camera'] = camera
instance.data['frameStartFtrack'] = \
instance.data["frameStartHandle"]
@ -156,9 +145,22 @@ class CollectReview(pyblish.api.InstancePlugin):
instance.data["audio"] = audio_data
# Convert enum attribute index to string.
index = instance.data.get("displayLights", 0)
display_lights = lib.DISPLAY_LIGHTS_VALUES[index]
if display_lights == "project_settings":
settings = instance.context.data["project_settings"]
settings = settings["maya"]["publish"]["ExtractPlayblast"]
settings = settings["capture_preset"]["Viewport Options"]
display_lights = settings["displayLights"]
instance.data["displayLights"] = display_lights
# Collect focal length.
if camera is None:
return
attr = camera + ".focalLength"
if get_attribute_input(attr):
if lib.get_attribute_input(attr):
start = instance.data["frameStart"]
end = instance.data["frameEnd"] + 1
focal_length = [

View file

@ -34,13 +34,15 @@ class ExtractPlayblast(publish.Extractor):
families = ["review"]
optional = True
capture_preset = {}
profiles = None
def _capture(self, preset):
self.log.info(
"Using preset:\n{}".format(
json.dumps(preset, sort_keys=True, indent=4)
if os.environ.get("OPENPYPE_DEBUG") == "1":
self.log.debug(
"Using preset: {}".format(
json.dumps(preset, indent=4, sort_keys=True)
)
)
)
path = capture.capture(log=self.log, **preset)
self.log.debug("playblast path {}".format(path))
@ -65,12 +67,25 @@ class ExtractPlayblast(publish.Extractor):
# get cameras
camera = instance.data["review_camera"]
preset = lib.load_capture_preset(data=self.capture_preset)
# Grab capture presets from the project settings
capture_presets = self.capture_preset
task_data = instance.data["anatomyData"].get("task", {})
capture_preset = lib.get_capture_preset(
task_data.get("name"),
task_data.get("type"),
instance.data["subset"],
instance.context.data["project_settings"],
self.log
)
preset = lib.load_capture_preset(data=capture_preset)
# "isolate_view" will already have been applied at creation, so we'll
# ignore it here.
preset.pop("isolate_view")
# Set resolution variables from capture presets
width_preset = capture_presets["Resolution"]["width"]
height_preset = capture_presets["Resolution"]["height"]
width_preset = capture_preset["Resolution"]["width"]
height_preset = capture_preset["Resolution"]["height"]
# Set resolution variables from asset values
asset_data = instance.data["assetEntity"]["data"]
asset_width = asset_data.get("resolutionWidth")
@ -115,14 +130,19 @@ class ExtractPlayblast(publish.Extractor):
cmds.currentTime(refreshFrameInt - 1, edit=True)
cmds.currentTime(refreshFrameInt, edit=True)
# Use displayLights setting from instance
key = "displayLights"
preset["viewport_options"][key] = instance.data[key]
# Override transparency if requested.
transparency = instance.data.get("transparency", 0)
if transparency != 0:
preset["viewport2_options"]["transparencyAlgorithm"] = transparency
# Isolate view is requested by having objects in the set besides a
# camera.
if preset.pop("isolate_view", False) and instance.data.get("isolate"):
# camera. If there is only 1 member it'll be the camera because we
# validate to have 1 camera only.
if instance.data["isolate"] and len(instance.data["setMembers"]) > 1:
preset["isolate"] = instance.data["setMembers"]
# Show/Hide image planes on request.
@ -157,7 +177,7 @@ class ExtractPlayblast(publish.Extractor):
)
override_viewport_options = (
capture_presets["Viewport Options"]["override_viewport_options"]
capture_preset["Viewport Options"]["override_viewport_options"]
)
# Force viewer to False in call to capture because we have our own
@ -233,8 +253,8 @@ class ExtractPlayblast(publish.Extractor):
collected_files = collected_files[0]
representation = {
"name": self.capture_preset["Codec"]["compression"],
"ext": self.capture_preset["Codec"]["compression"],
"name": capture_preset["Codec"]["compression"],
"ext": capture_preset["Codec"]["compression"],
"files": collected_files,
"stagingDir": stagingdir,
"frameStart": start,

View file

@ -1,6 +1,7 @@
import os
import glob
import tempfile
import json
import capture
@ -27,22 +28,25 @@ class ExtractThumbnail(publish.Extractor):
camera = instance.data["review_camera"]
maya_setting = instance.context.data["project_settings"]["maya"]
plugin_setting = maya_setting["publish"]["ExtractPlayblast"]
capture_preset = plugin_setting["capture_preset"]
task_data = instance.data["anatomyData"].get("task", {})
capture_preset = lib.get_capture_preset(
task_data.get("name"),
task_data.get("type"),
instance.data["subset"],
instance.context.data["project_settings"],
self.log
)
preset = lib.load_capture_preset(data=capture_preset)
# "isolate_view" will already have been applied at creation, so we'll
# ignore it here.
preset.pop("isolate_view")
override_viewport_options = (
capture_preset["Viewport Options"]["override_viewport_options"]
)
try:
preset = lib.load_capture_preset(data=capture_preset)
except KeyError as ke:
self.log.error("Error loading capture presets: {}".format(str(ke)))
preset = {}
self.log.info("Using viewport preset: {}".format(preset))
# preset["off_screen"] = False
preset["camera"] = camera
preset["start_frame"] = instance.data["frameStart"]
preset["end_frame"] = instance.data["frameStart"]
@ -58,10 +62,9 @@ class ExtractThumbnail(publish.Extractor):
"overscan": 1.0,
"depthOfField": cmds.getAttr("{0}.depthOfField".format(camera)),
}
capture_presets = capture_preset
# Set resolution variables from capture presets
width_preset = capture_presets["Resolution"]["width"]
height_preset = capture_presets["Resolution"]["height"]
width_preset = capture_preset["Resolution"]["width"]
height_preset = capture_preset["Resolution"]["height"]
# Set resolution variables from asset values
asset_data = instance.data["assetEntity"]["data"]
asset_width = asset_data.get("resolutionWidth")
@ -104,14 +107,19 @@ class ExtractThumbnail(publish.Extractor):
cmds.currentTime(refreshFrameInt - 1, edit=True)
cmds.currentTime(refreshFrameInt, edit=True)
# Use displayLights setting from instance
key = "displayLights"
preset["viewport_options"][key] = instance.data[key]
# Override transparency if requested.
transparency = instance.data.get("transparency", 0)
if transparency != 0:
preset["viewport2_options"]["transparencyAlgorithm"] = transparency
# Isolate view is requested by having objects in the set besides a
# camera.
if preset.pop("isolate_view", False) and instance.data.get("isolate"):
# camera. If there is only 1 member it'll be the camera because we
# validate to have 1 camera only.
if instance.data["isolate"] and len(instance.data["setMembers"]) > 1:
preset["isolate"] = instance.data["setMembers"]
# Show or Hide Image Plane
@ -139,6 +147,13 @@ class ExtractThumbnail(publish.Extractor):
preset.update(panel_preset)
cmds.setFocus(panel)
if os.environ.get("OPENPYPE_DEBUG") == "1":
self.log.debug(
"Using preset: {}".format(
json.dumps(preset, indent=4, sort_keys=True)
)
)
path = capture.capture(**preset)
playblast = self._fix_playblast_output_path(path)

View file

@ -65,9 +65,10 @@ class ExtractXgen(publish.Extractor):
)
cmds.delete(set(children) - set(shapes))
duplicate_transform = cmds.parent(
duplicate_transform, world=True
)[0]
if cmds.listRelatives(duplicate_transform, parent=True):
duplicate_transform = cmds.parent(
duplicate_transform, world=True
)[0]
duplicate_nodes.append(duplicate_transform)

View file

@ -255,7 +255,7 @@ class ValidateMeshHasOverlappingUVs(pyblish.api.InstancePlugin):
# Store original uv set
original_current_uv_set = cmds.polyUVSet(mesh,
query=True,
currentUVSet=True)
currentUVSet=True)[0]
overlapping_faces = []
for uv_set in cmds.polyUVSet(mesh, query=True, allUVSets=True):

View file

@ -0,0 +1,30 @@
import pyblish.api
from openpype.pipeline.publish import (
ValidateContentsOrder, PublishValidationError
)
class ValidateReview(pyblish.api.InstancePlugin):
"""Validate review."""
order = ValidateContentsOrder
label = "Validate Review"
families = ["review"]
def process(self, instance):
cameras = instance.data["cameras"]
# validate required settings
if len(cameras) == 0:
raise PublishValidationError(
"No camera found in review instance: {}".format(instance)
)
elif len(cameras) > 2:
raise PublishValidationError(
"Only a single camera is allowed for a review instance but "
"more than one camera found in review instance: {}. "
"Cameras found: {}".format(instance, ", ".join(cameras))
)
self.log.debug('camera: {}'.format(instance.data["review_camera"]))

View file

@ -19,7 +19,7 @@ class ValidateSingleAssembly(pyblish.api.InstancePlugin):
order = ValidateContentsOrder
hosts = ['maya']
families = ['rig', 'animation']
families = ['rig']
label = 'Single Assembly'
def process(self, instance):

View file

@ -57,3 +57,16 @@ class ValidateXgen(pyblish.api.InstancePlugin):
json.dumps(inactive_modifiers, indent=4, sort_keys=True)
)
)
# We need a namespace else there will be a naming conflict when
# extracting because of stripping namespaces and parenting to world.
node_names = [instance.data["xgmPalette"]]
for _, connections in instance.data["xgenConnections"].items():
node_names.append(connections["transform"].split(".")[0])
non_namespaced_nodes = [n for n in node_names if ":" not in n]
if non_namespaced_nodes:
raise PublishValidationError(
"Could not find namespace on {}. Namespace is required for"
" xgen publishing.".format(non_namespaced_nodes)
)

View file

@ -1,5 +1,4 @@
import os
from functools import partial
from openpype.settings import get_project_settings
from openpype.pipeline import install_host
@ -13,24 +12,41 @@ install_host(host)
print("Starting OpenPype usersetup...")
project_settings = get_project_settings(os.environ['AVALON_PROJECT'])
# Loading plugins explicitly.
explicit_plugins_loading = project_settings["maya"]["explicit_plugins_loading"]
if explicit_plugins_loading["enabled"]:
def _explicit_load_plugins():
for plugin in explicit_plugins_loading["plugins_to_load"]:
if plugin["enabled"]:
print("Loading plug-in: " + plugin["name"])
try:
cmds.loadPlugin(plugin["name"], quiet=True)
except RuntimeError as e:
print(e)
# We need to load plugins deferred as loading them directly does not work
# correctly due to Maya's initialization.
cmds.evalDeferred(
_explicit_load_plugins,
lowestPriority=True
)
# Open Workfile Post Initialization.
key = "OPENPYPE_OPEN_WORKFILE_POST_INITIALIZATION"
if bool(int(os.environ.get(key, "0"))):
def _log_and_open():
path = os.environ["AVALON_LAST_WORKFILE"]
print("Opening \"{}\"".format(path))
cmds.file(path, open=True, force=True)
cmds.evalDeferred(
partial(
cmds.file,
os.environ["AVALON_LAST_WORKFILE"],
open=True,
force=True
),
_log_and_open,
lowestPriority=True
)
# Build a shelf.
settings = get_project_settings(os.environ['AVALON_PROJECT'])
shelf_preset = settings['maya'].get('project_shelf')
shelf_preset = project_settings['maya'].get('project_shelf')
if shelf_preset:
project = os.environ["AVALON_PROJECT"]

View file

@ -250,7 +250,7 @@ class MayaLookAssignerWindow(QtWidgets.QWidget):
if vp in nodes:
vrayproxy_assign_look(vp, subset_name)
nodes = list(set(item["nodes"]).difference(vray_proxies))
nodes = list(set(nodes).difference(vray_proxies))
else:
self.echo(
"Could not assign to VRayProxy because vrayformaya plugin "
@ -260,17 +260,18 @@ class MayaLookAssignerWindow(QtWidgets.QWidget):
# Assign Arnold Standin look.
if cmds.pluginInfo("mtoa", query=True, loaded=True):
arnold_standins = set(cmds.ls(type="aiStandIn", long=True))
for standin in arnold_standins:
if standin in nodes:
arnold_standin.assign_look(standin, subset_name)
nodes = list(set(nodes).difference(arnold_standins))
else:
self.echo(
"Could not assign to aiStandIn because mtoa plugin is not "
"loaded."
)
nodes = list(set(item["nodes"]).difference(arnold_standins))
# Assign look
if nodes:
assign_look_by_version(nodes, version_id=version["_id"])

View file

@ -36,11 +36,9 @@ class BatchMovieCreator(TrayPublishCreator):
# Position batch creator after simple creators
order = 110
def __init__(self, project_settings, *args, **kwargs):
super(BatchMovieCreator, self).__init__(project_settings,
*args, **kwargs)
def apply_settings(self, project_settings, system_settings):
creator_settings = (
project_settings["traypublisher"]["BatchMovieCreator"]
project_settings["traypublisher"]["create"]["BatchMovieCreator"]
)
self.default_variants = creator_settings["default_variants"]
self.default_tasks = creator_settings["default_tasks"]
@ -151,4 +149,3 @@ class BatchMovieCreator(TrayPublishCreator):
File names must then contain only asset name, or asset name + version.
(eg. 'chair.mov', 'chair_v001.mov', not really safe `my_chair_v001.mov`
"""

View file

@ -144,7 +144,7 @@ class ExtractSequence(pyblish.api.Extractor):
# Fill tags and new families from project settings
tags = []
if family_lowered == "review":
if "review" in instance.data["families"]:
tags.append("review")
# Sequence of one frame

View file

@ -24,7 +24,7 @@ class UnrealPrelaunchHook(PreLaunchHook):
"""Hook to handle launching Unreal.
This hook will check if current workfile path has Unreal
project inside. IF not, it initialize it and finally it pass
project inside. IF not, it initializes it, and finally it pass
path to the project by environment variable to Unreal launcher
shell script.
@ -141,6 +141,7 @@ class UnrealPrelaunchHook(PreLaunchHook):
def execute(self):
"""Hook entry method."""
workdir = self.launch_context.env["AVALON_WORKDIR"]
executable = str(self.launch_context.executable)
engine_version = self.app_name.split("/")[-1].replace("-", ".")
try:
if int(engine_version.split(".")[0]) < 4 and \
@ -152,7 +153,7 @@ class UnrealPrelaunchHook(PreLaunchHook):
# there can be string in minor version and in that case
# int cast is failing. This probably happens only with
# early access versions and is of no concert for this check
# so lets keep it quite.
# so let's keep it quiet.
...
unreal_project_filename = self._get_work_filename()
@ -183,26 +184,6 @@ class UnrealPrelaunchHook(PreLaunchHook):
f"[ {engine_version} ]"
))
detected = unreal_lib.get_engine_versions(self.launch_context.env)
detected_str = ', '.join(detected.keys()) or 'none'
self.log.info((
f"{self.signature} detected UE versions: "
f"[ {detected_str} ]"
))
if not detected:
raise ApplicationNotFound("No Unreal Engines are found.")
engine_version = ".".join(engine_version.split(".")[:2])
if engine_version not in detected.keys():
raise ApplicationLaunchFailed((
f"{self.signature} requested version not "
f"detected [ {engine_version} ]"
))
ue_path = unreal_lib.get_editor_exe_path(
Path(detected[engine_version]), engine_version)
self.launch_context.launch_args = [ue_path.as_posix()]
project_path.mkdir(parents=True, exist_ok=True)
# Set "OPENPYPE_UNREAL_PLUGIN" to current process environment for
@ -217,7 +198,9 @@ class UnrealPrelaunchHook(PreLaunchHook):
if self.launch_context.env.get(env_key):
os.environ[env_key] = self.launch_context.env[env_key]
engine_path: Path = Path(detected[engine_version])
# engine_path points to the specific Unreal Engine root
# so, we are going up from the executable itself 3 levels.
engine_path: Path = Path(executable).parents[3]
if not unreal_lib.check_plugin_existence(engine_path):
self.exec_plugin_install(engine_path)

View file

@ -23,6 +23,8 @@ def get_engine_versions(env=None):
Location can be overridden by `UNREAL_ENGINE_LOCATION` environment
variable.
.. deprecated:: 3.15.4
Args:
env (dict, optional): Environment to use.
@ -103,6 +105,8 @@ def _win_get_engine_versions():
This file is JSON file listing installed stuff, Unreal engines
are marked with `"AppName" = "UE_X.XX"`` like `UE_4.24`
.. deprecated:: 3.15.4
Returns:
dict: version as a key and path as a value.
@ -122,6 +126,8 @@ def _darwin_get_engine_version() -> dict:
It works the same as on Windows, just JSON file location is different.
.. deprecated:: 3.15.4
Returns:
dict: version as a key and path as a value.
@ -144,6 +150,8 @@ def _darwin_get_engine_version() -> dict:
def _parse_launcher_locations(install_json_path: str) -> dict:
"""This will parse locations from json file.
.. deprecated:: 3.15.4
Args:
install_json_path (str): Path to `LauncherInstalled.dat`.

View file

@ -1,41 +0,0 @@
import clique
import pyblish.api
class ValidateSequenceFrames(pyblish.api.InstancePlugin):
"""Ensure the sequence of frames is complete
The files found in the folder are checked against the frameStart and
frameEnd of the instance. If the first or last file is not
corresponding with the first or last frame it is flagged as invalid.
"""
order = pyblish.api.ValidatorOrder
label = "Validate Sequence Frames"
families = ["render"]
hosts = ["unreal"]
optional = True
def process(self, instance):
representations = instance.data.get("representations")
for repr in representations:
patterns = [clique.PATTERNS["frames"]]
collections, remainder = clique.assemble(
repr["files"], minimum_items=1, patterns=patterns)
assert not remainder, "Must not have remainder"
assert len(collections) == 1, "Must detect single collection"
collection = collections[0]
frames = list(collection.indexes)
current_range = (frames[0], frames[-1])
required_range = (instance.data["frameStart"],
instance.data["frameEnd"])
if current_range != required_range:
raise ValueError(f"Invalid frame range: {current_range} - "
f"expected: {required_range}")
missing = collection.holes().indexes
assert not missing, "Missing frames: %s" % (missing,)

View file

@ -142,10 +142,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
job_info.Pool = instance.data.get("primaryPool")
job_info.SecondaryPool = instance.data.get("secondaryPool")
job_info.ChunkSize = instance.data.get("chunkSize", 10)
job_info.Comment = context.data.get("comment")
job_info.Priority = instance.data.get("priority", self.priority)
job_info.FramesPerTask = instance.data.get("framesPerTask", 1)
if self.group != "none" and self.group:
job_info.Group = self.group
@ -327,6 +325,11 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
job_info = copy.deepcopy(payload_job_info)
plugin_info = copy.deepcopy(payload_plugin_info)
# Force plugin reload for vray cause the region does not get flushed
# between tile renders.
if plugin_info["Renderer"] == "vray":
job_info.ForceReloadPlugin = True
# if we have sequence of files, we need to create tile job for
# every frame
job_info.TileJob = True
@ -436,6 +439,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
assembly_payloads = []
output_dir = self.job_info.OutputDirectory[0]
config_files = []
for file in assembly_files:
frame = re.search(R_FRAME_NUMBER, file).group("frame")
@ -461,6 +465,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
)
)
config_files.append(config_file)
try:
if not os.path.isdir(output_dir):
os.makedirs(output_dir)
@ -469,8 +474,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
self.log.warning("Path is unreachable: "
"`{}`".format(output_dir))
assembly_plugin_info["ConfigFile"] = config_file
with open(config_file, "w") as cf:
print("TileCount={}".format(tiles_count), file=cf)
print("ImageFileName={}".format(file), file=cf)
@ -479,6 +482,10 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
print("ImageHeight={}".format(
instance.data.get("resolutionHeight")), file=cf)
reversed_y = False
if plugin_info["Renderer"] == "arnold":
reversed_y = True
with open(config_file, "a") as cf:
# Need to reverse the order of the y tiles, because image
# coordinates are calculated from bottom left corner.
@ -489,7 +496,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
instance.data.get("resolutionWidth"),
instance.data.get("resolutionHeight"),
payload_plugin_info["OutputFilePrefix"],
reversed_y=True
reversed_y=reversed_y
)[1]
for k, v in sorted(tiles.items()):
print("{}={}".format(k, v), file=cf)
@ -518,6 +525,11 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
instance.data["assemblySubmissionJobs"] = assembly_job_ids
# Remove config files to avoid confusion about where data is coming
# from in Deadline.
for config_file in config_files:
os.remove(config_file)
def _get_maya_payload(self, data):
job_info = copy.deepcopy(self.job_info)
@ -878,8 +890,6 @@ def _format_tiles(
out["PluginInfo"]["RegionRight{}".format(tile)] = right
# Tile config
cfg["Tile{}".format(tile)] = new_filename
cfg["Tile{}Tile".format(tile)] = new_filename
cfg["Tile{}FileName".format(tile)] = new_filename
cfg["Tile{}X".format(tile)] = left
cfg["Tile{}Y".format(tile)] = top

View file

@ -354,6 +354,61 @@ def publish_plugins_discover(paths=None):
return result
def _get_plugin_settings(host_name, project_settings, plugin, log):
"""Get plugin settings based on host name and plugin name.
Args:
host_name (str): Name of host.
project_settings (dict[str, Any]): Project settings.
plugin (pyliblish.Plugin): Plugin where settings are applied.
log (logging.Logger): Logger to log messages.
Returns:
dict[str, Any]: Plugin settings {'attribute': 'value'}.
"""
# Use project settings from host name category when available
try:
return (
project_settings
[host_name]
["publish"]
[plugin.__name__]
)
except KeyError:
pass
# Settings category determined from path
# - usually path is './<category>/plugins/publish/<plugin file>'
# - category can be host name of addon name ('maya', 'deadline', ...)
filepath = os.path.normpath(inspect.getsourcefile(plugin))
split_path = filepath.rsplit(os.path.sep, 5)
if len(split_path) < 4:
log.warning(
'plugin path too short to extract host {}'.format(filepath)
)
return {}
category_from_file = split_path[-4]
plugin_kind = split_path[-2]
# TODO: change after all plugins are moved one level up
if category_from_file == "openpype":
category_from_file = "global"
try:
return (
project_settings
[category_from_file]
[plugin_kind]
[plugin.__name__]
)
except KeyError:
pass
return {}
def filter_pyblish_plugins(plugins):
"""Pyblish plugin filter which applies OpenPype settings.
@ -372,21 +427,21 @@ def filter_pyblish_plugins(plugins):
# TODO: Don't use host from 'pyblish.api' but from defined host by us.
# - kept becau on farm is probably used host 'shell' which propably
# affect how settings are applied there
host = pyblish.api.current_host()
host_name = pyblish.api.current_host()
project_name = os.environ.get("AVALON_PROJECT")
project_setting = get_project_settings(project_name)
project_settings = get_project_settings(project_name)
system_settings = get_system_settings()
# iterate over plugins
for plugin in plugins[:]:
# Apply settings to plugins
if hasattr(plugin, "apply_settings"):
# Use classmethod 'apply_settings'
# - can be used to target settings from custom settings place
# - skip default behavior when successful
try:
# Use classmethod 'apply_settings'
# - can be used to target settings from custom settings place
# - skip default behavior when successful
plugin.apply_settings(project_setting, system_settings)
continue
plugin.apply_settings(project_settings, system_settings)
except Exception:
log.warning(
@ -395,53 +450,20 @@ def filter_pyblish_plugins(plugins):
).format(plugin.__name__),
exc_info=True
)
try:
config_data = (
project_setting
[host]
["publish"]
[plugin.__name__]
else:
# Automated
plugin_settins = _get_plugin_settings(
host_name, project_settings, plugin, log
)
except KeyError:
# host determined from path
file = os.path.normpath(inspect.getsourcefile(plugin))
file = os.path.normpath(file)
split_path = file.split(os.path.sep)
if len(split_path) < 4:
log.warning(
'plugin path too short to extract host {}'.format(file)
)
continue
host_from_file = split_path[-4]
plugin_kind = split_path[-2]
# TODO: change after all plugins are moved one level up
if host_from_file == "openpype":
host_from_file = "global"
try:
config_data = (
project_setting
[host_from_file]
[plugin_kind]
[plugin.__name__]
)
except KeyError:
continue
for option, value in config_data.items():
if option == "enabled" and value is False:
log.info('removing plugin {}'.format(plugin.__name__))
plugins.remove(plugin)
else:
log.info('setting {}:{} on plugin {}'.format(
for option, value in plugin_settins.items():
log.info("setting {}:{} on plugin {}".format(
option, value, plugin.__name__))
setattr(plugin, option, value)
# Remove disabled plugins
if getattr(plugin, "enabled", True) is False:
plugins.remove(plugin)
def find_close_plugin(close_plugin_name, log):
if close_plugin_name:

View file

@ -158,7 +158,7 @@ class AbstractTemplateBuilder(object):
def linked_asset_docs(self):
if self._linked_asset_docs is None:
self._linked_asset_docs = get_linked_assets(
self.current_asset_doc
self.project_name, self.current_asset_doc
)
return self._linked_asset_docs
@ -1151,13 +1151,10 @@ class PlaceholderItem(object):
return self._log
def __repr__(self):
name = None
if hasattr("name", self):
name = self.name
if hasattr("_scene_identifier ", self):
name = self._scene_identifier
return "< {} {} >".format(self.__class__.__name__, name)
return "< {} {} >".format(
self.__class__.__name__,
self._scene_identifier
)
@property
def order(self):
@ -1419,16 +1416,7 @@ class PlaceholderLoadMixin(object):
"family": [placeholder.data["family"]]
}
elif builder_type != "linked_asset":
context_filters = {
"asset": [re.compile(placeholder.data["asset"])],
"subset": [re.compile(placeholder.data["subset"])],
"hierarchy": [re.compile(placeholder.data["hierarchy"])],
"representation": [placeholder.data["representation"]],
"family": [placeholder.data["family"]]
}
else:
elif builder_type == "linked_asset":
asset_regex = re.compile(placeholder.data["asset"])
linked_asset_names = []
for asset_doc in linked_asset_docs:
@ -1444,6 +1432,15 @@ class PlaceholderLoadMixin(object):
"family": [placeholder.data["family"]],
}
else:
context_filters = {
"asset": [re.compile(placeholder.data["asset"])],
"subset": [re.compile(placeholder.data["subset"])],
"hierarchy": [re.compile(placeholder.data["hierarchy"])],
"representation": [placeholder.data["representation"]],
"family": [placeholder.data["family"]]
}
return list(get_representations(
project_name,
context_filters=context_filters

View file

@ -50,7 +50,6 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
project_name = context.data["projectName"]
self.fill_missing_asset_docs(context, project_name)
self.fill_instance_data_from_asset(context)
self.fill_latest_versions(context, project_name)
self.fill_anatomy_data(context)
@ -115,23 +114,6 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
"Not found asset documents with names \"{}\"."
).format(joined_asset_names))
def fill_instance_data_from_asset(self, context):
for instance in context:
asset_doc = instance.data.get("assetEntity")
if not asset_doc:
continue
asset_data = asset_doc["data"]
for key in (
"fps",
"frameStart",
"frameEnd",
"handleStart",
"handleEnd",
):
if key not in instance.data and key in asset_data:
instance.data[key] = asset_data[key]
def fill_latest_versions(self, context, project_name):
"""Try to find latest version for each instance's subset.

View file

@ -49,7 +49,8 @@ class ExtractBurnin(publish.Extractor):
"webpublisher",
"aftereffects",
"photoshop",
"flame"
"flame",
"houdini"
# "resolve"
]
@ -78,9 +79,10 @@ class ExtractBurnin(publish.Extractor):
self.log.warning("No profiles present for create burnin")
return
# QUESTION what is this for and should we raise an exception?
if "representations" not in instance.data:
raise RuntimeError("Burnin needs already created mov to work on.")
if not instance.data.get("representations"):
self.log.info(
"Instance does not have filled representations. Skipping")
return
self.main_process(instance)

View file

@ -44,6 +44,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
"nuke",
"maya",
"blender",
"houdini",
"shell",
"hiero",
"premiere",

View file

@ -1,3 +1,7 @@
import os
import re
import clique
import pyblish.api
@ -7,28 +11,51 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin):
The files found in the folder are checked against the startFrame and
endFrame of the instance. If the first or last file is not
corresponding with the first or last frame it is flagged as invalid.
Used regular expression pattern handles numbers in the file names
(eg "Main_beauty.v001.1001.exr", "Main_beauty_v001.1001.exr",
"Main_beauty.1001.1001.exr") but not numbers behind frames (eg.
"Main_beauty.1001.v001.exr")
"""
order = pyblish.api.ValidatorOrder
label = "Validate Sequence Frames"
families = ["imagesequence"]
hosts = ["shell"]
families = ["imagesequence", "render"]
hosts = ["shell", "unreal"]
def process(self, instance):
representations = instance.data.get("representations")
if not representations:
return
for repr in representations:
repr_files = repr["files"]
if isinstance(repr_files, str):
continue
collection = instance[0]
self.log.info(collection)
ext = repr.get("ext")
if not ext:
_, ext = os.path.splitext(repr_files[0])
elif not ext.startswith("."):
ext = ".{}".format(ext)
pattern = r"\D?(?P<index>(?P<padding>0*)\d+){}$".format(
re.escape(ext))
patterns = [pattern]
frames = list(collection.indexes)
collections, remainder = clique.assemble(
repr_files, minimum_items=1, patterns=patterns)
current_range = (frames[0], frames[-1])
required_range = (instance.data["frameStart"],
instance.data["frameEnd"])
assert not remainder, "Must not have remainder"
assert len(collections) == 1, "Must detect single collection"
collection = collections[0]
frames = list(collection.indexes)
if current_range != required_range:
raise ValueError("Invalid frame range: {0} - "
"expected: {1}".format(current_range,
required_range))
current_range = (frames[0], frames[-1])
required_range = (instance.data["frameStart"],
instance.data["frameEnd"])
missing = collection.holes().indexes
assert not missing, "Missing frames: %s" % (missing,)
if current_range != required_range:
raise ValueError(f"Invalid frame range: {current_range} - "
f"expected: {required_range}")
missing = collection.holes().indexes
assert not missing, "Missing frames: %s" % (missing,)

View file

@ -251,15 +251,16 @@
}
},
{
"families": [],
"families": ["review"],
"hosts": [
"maya"
"maya",
"houdini"
],
"task_types": [],
"task_names": [],
"subsets": [],
"burnins": {
"maya_burnin": {
"focal_length_burnin": {
"TOP_LEFT": "{yy}-{mm}-{dd}",
"TOP_CENTERED": "{focalLength:.2f} mm",
"TOP_RIGHT": "{anatomy[version]}",

View file

@ -21,7 +21,8 @@
"floatLut": "linear",
"logLut": "Cineon",
"viewerLut": "sRGB",
"thumbnailLut": "sRGB"
"thumbnailLut": "sRGB",
"monitorOutLut": "sRGB"
},
"regexInputs": {
"inputs": [

View file

@ -1,5 +1,414 @@
{
"open_workfile_post_initialization": false,
"explicit_plugins_loading": {
"enabled": false,
"plugins_to_load": [
{
"enabled": false,
"name": "AbcBullet"
},
{
"enabled": true,
"name": "AbcExport"
},
{
"enabled": true,
"name": "AbcImport"
},
{
"enabled": false,
"name": "animImportExport"
},
{
"enabled": false,
"name": "ArubaTessellator"
},
{
"enabled": false,
"name": "ATFPlugin"
},
{
"enabled": false,
"name": "atomImportExport"
},
{
"enabled": false,
"name": "AutodeskPacketFile"
},
{
"enabled": false,
"name": "autoLoader"
},
{
"enabled": false,
"name": "bifmeshio"
},
{
"enabled": false,
"name": "bifrostGraph"
},
{
"enabled": false,
"name": "bifrostshellnode"
},
{
"enabled": false,
"name": "bifrostvisplugin"
},
{
"enabled": false,
"name": "blast2Cmd"
},
{
"enabled": false,
"name": "bluePencil"
},
{
"enabled": false,
"name": "Boss"
},
{
"enabled": false,
"name": "bullet"
},
{
"enabled": true,
"name": "cacheEvaluator"
},
{
"enabled": false,
"name": "cgfxShader"
},
{
"enabled": false,
"name": "cleanPerFaceAssignment"
},
{
"enabled": false,
"name": "clearcoat"
},
{
"enabled": false,
"name": "convertToComponentTags"
},
{
"enabled": false,
"name": "curveWarp"
},
{
"enabled": false,
"name": "ddsFloatReader"
},
{
"enabled": true,
"name": "deformerEvaluator"
},
{
"enabled": false,
"name": "dgProfiler"
},
{
"enabled": false,
"name": "drawUfe"
},
{
"enabled": false,
"name": "dx11Shader"
},
{
"enabled": false,
"name": "fbxmaya"
},
{
"enabled": false,
"name": "fltTranslator"
},
{
"enabled": false,
"name": "freeze"
},
{
"enabled": false,
"name": "Fur"
},
{
"enabled": false,
"name": "gameFbxExporter"
},
{
"enabled": false,
"name": "gameInputDevice"
},
{
"enabled": false,
"name": "GamePipeline"
},
{
"enabled": false,
"name": "gameVertexCount"
},
{
"enabled": false,
"name": "geometryReport"
},
{
"enabled": false,
"name": "geometryTools"
},
{
"enabled": false,
"name": "glslShader"
},
{
"enabled": true,
"name": "GPUBuiltInDeformer"
},
{
"enabled": false,
"name": "gpuCache"
},
{
"enabled": false,
"name": "hairPhysicalShader"
},
{
"enabled": false,
"name": "ik2Bsolver"
},
{
"enabled": false,
"name": "ikSpringSolver"
},
{
"enabled": false,
"name": "invertShape"
},
{
"enabled": false,
"name": "lges"
},
{
"enabled": false,
"name": "lookdevKit"
},
{
"enabled": false,
"name": "MASH"
},
{
"enabled": false,
"name": "matrixNodes"
},
{
"enabled": false,
"name": "mayaCharacterization"
},
{
"enabled": false,
"name": "mayaHIK"
},
{
"enabled": false,
"name": "MayaMuscle"
},
{
"enabled": false,
"name": "mayaUsdPlugin"
},
{
"enabled": false,
"name": "mayaVnnPlugin"
},
{
"enabled": false,
"name": "melProfiler"
},
{
"enabled": false,
"name": "meshReorder"
},
{
"enabled": true,
"name": "modelingToolkit"
},
{
"enabled": false,
"name": "mtoa"
},
{
"enabled": false,
"name": "mtoh"
},
{
"enabled": false,
"name": "nearestPointOnMesh"
},
{
"enabled": true,
"name": "objExport"
},
{
"enabled": false,
"name": "OneClick"
},
{
"enabled": false,
"name": "OpenEXRLoader"
},
{
"enabled": false,
"name": "pgYetiMaya"
},
{
"enabled": false,
"name": "pgyetiVrayMaya"
},
{
"enabled": false,
"name": "polyBoolean"
},
{
"enabled": false,
"name": "poseInterpolator"
},
{
"enabled": false,
"name": "quatNodes"
},
{
"enabled": false,
"name": "randomizerDevice"
},
{
"enabled": false,
"name": "redshift4maya"
},
{
"enabled": true,
"name": "renderSetup"
},
{
"enabled": false,
"name": "retargeterNodes"
},
{
"enabled": false,
"name": "RokokoMotionLibrary"
},
{
"enabled": false,
"name": "rotateHelper"
},
{
"enabled": false,
"name": "sceneAssembly"
},
{
"enabled": false,
"name": "shaderFXPlugin"
},
{
"enabled": false,
"name": "shotCamera"
},
{
"enabled": false,
"name": "snapTransform"
},
{
"enabled": false,
"name": "stage"
},
{
"enabled": true,
"name": "stereoCamera"
},
{
"enabled": false,
"name": "stlTranslator"
},
{
"enabled": false,
"name": "studioImport"
},
{
"enabled": false,
"name": "Substance"
},
{
"enabled": false,
"name": "substancelink"
},
{
"enabled": false,
"name": "substancemaya"
},
{
"enabled": false,
"name": "substanceworkflow"
},
{
"enabled": false,
"name": "svgFileTranslator"
},
{
"enabled": false,
"name": "sweep"
},
{
"enabled": false,
"name": "testify"
},
{
"enabled": false,
"name": "tiffFloatReader"
},
{
"enabled": false,
"name": "timeSliderBookmark"
},
{
"enabled": false,
"name": "Turtle"
},
{
"enabled": false,
"name": "Type"
},
{
"enabled": false,
"name": "udpDevice"
},
{
"enabled": false,
"name": "ufeSupport"
},
{
"enabled": false,
"name": "Unfold3D"
},
{
"enabled": false,
"name": "VectorRender"
},
{
"enabled": false,
"name": "vrayformaya"
},
{
"enabled": false,
"name": "vrayvolumegrid"
},
{
"enabled": false,
"name": "xgenToolkit"
},
{
"enabled": false,
"name": "xgenVray"
}
]
},
"imageio": {
"ocio_config": {
"enabled": false,
@ -911,7 +1320,8 @@
"displayFilmOrigin": false,
"overscan": 1.0
}
}
},
"profiles": []
},
"ExtractMayaSceneRaw": {
"enabled": true,
@ -1047,6 +1457,10 @@
125,
255
]
},
"reference_loader": {
"namespace": "{asset_name}_{subset}_##",
"group_name": "_GRP"
}
},
"workfile_build": {
@ -1140,6 +1554,10 @@
}
]
},
"include_handles": {
"include_handles_default": false,
"per_task_type": []
},
"templated_workfile_build": {
"profiles": []
},

View file

@ -542,45 +542,10 @@
"create_first_version": false,
"custom_templates": [],
"builder_on_start": false,
"profiles": [
{
"task_types": [],
"tasks": [],
"current_context": [
{
"subset_name_filters": [],
"families": [
"render",
"plate"
],
"repre_names": [
"exr",
"dpx",
"mov",
"mp4",
"h264"
],
"loaders": [
"LoadClip"
]
}
],
"linked_assets": []
}
]
"profiles": []
},
"templated_workfile_build": {
"profiles": [
{
"task_types": [
"Compositing"
],
"task_names": [],
"path": "{project[name]}/templates/comp.nk",
"keep_placeholder": true,
"create_first_version": true
}
]
"profiles": []
},
"filters": {}
}

View file

@ -303,16 +303,18 @@
]
}
},
"BatchMovieCreator": {
"default_variants": [
"Main"
],
"default_tasks": [
"Compositing"
],
"extensions": [
".mov"
]
"create": {
"BatchMovieCreator": {
"default_variants": [
"Main"
],
"default_tasks": [
"Compositing"
],
"extensions": [
".mov"
]
}
},
"publish": {
"ValidateFrameRange": {

View file

@ -133,7 +133,9 @@
"linux": []
},
"arguments": {
"windows": ["-U MAXScript {OPENPYPE_ROOT}\\openpype\\hosts\\max\\startup\\startup.ms"],
"windows": [
"-U MAXScript {OPENPYPE_ROOT}\\openpype\\hosts\\max\\startup\\startup.ms"
],
"darwin": [],
"linux": []
},
@ -361,9 +363,15 @@
]
},
"arguments": {
"windows": ["--nukeassist"],
"darwin": ["--nukeassist"],
"linux": ["--nukeassist"]
"windows": [
"--nukeassist"
],
"darwin": [
"--nukeassist"
],
"linux": [
"--nukeassist"
]
},
"environment": {}
},
@ -379,9 +387,15 @@
]
},
"arguments": {
"windows": ["--nukeassist"],
"darwin": ["--nukeassist"],
"linux": ["--nukeassist"]
"windows": [
"--nukeassist"
],
"darwin": [
"--nukeassist"
],
"linux": [
"--nukeassist"
]
},
"environment": {}
},
@ -397,9 +411,15 @@
]
},
"arguments": {
"windows": ["--nukeassist"],
"darwin": ["--nukeassist"],
"linux": ["--nukeassist"]
"windows": [
"--nukeassist"
],
"darwin": [
"--nukeassist"
],
"linux": [
"--nukeassist"
]
},
"environment": {}
},
@ -415,9 +435,15 @@
]
},
"arguments": {
"windows": ["--nukeassist"],
"darwin": ["--nukeassist"],
"linux": ["--nukeassist"]
"windows": [
"--nukeassist"
],
"darwin": [
"--nukeassist"
],
"linux": [
"--nukeassist"
]
},
"environment": {}
},
@ -433,9 +459,15 @@
]
},
"arguments": {
"windows": ["--nukeassist"],
"darwin": ["--nukeassist"],
"linux": ["--nukeassist"]
"windows": [
"--nukeassist"
],
"darwin": [
"--nukeassist"
],
"linux": [
"--nukeassist"
]
},
"environment": {}
},
@ -449,9 +481,15 @@
"linux": []
},
"arguments": {
"windows": ["--nukeassist"],
"darwin": ["--nukeassist"],
"linux": ["--nukeassist"]
"windows": [
"--nukeassist"
],
"darwin": [
"--nukeassist"
],
"linux": [
"--nukeassist"
]
},
"environment": {}
},
@ -1450,21 +1488,45 @@
"label": "Unreal Editor",
"icon": "{}/app_icons/ue4.png",
"host_name": "unreal",
"environment": {},
"environment": {
"UE_PYTHONPATH": "{PYTHONPATH}"
},
"variants": {
"4-27": {
"use_python_2": false,
"environment": {}
},
"5-0": {
"use_python_2": false,
"environment": {
"UE_PYTHONPATH": "{PYTHONPATH}"
}
"executables": {
"windows": [
"C:\\Program Files\\Epic Games\\UE_5.0\\Engine\\Binaries\\Win64\\UnrealEditor.exe"
],
"darwin": [],
"linux": []
},
"arguments": {
"windows": [],
"darwin": [],
"linux": []
},
"environment": {}
},
"5-1": {
"use_python_2": false,
"executables": {
"windows": [
"C:\\Program Files\\Epic Games\\UE_5.1\\Engine\\Binaries\\Win64\\UnrealEditor.exe"
],
"darwin": [],
"linux": []
},
"arguments": {
"windows": [],
"darwin": [],
"linux": []
},
"environment": {}
},
"__dynamic_keys_labels__": {
"4-27": "4.27",
"5-0": "5.0"
"5-1": "Unreal 5.1",
"5-0": "Unreal 5.0"
}
}
},

View file

@ -11,8 +11,10 @@ class ColorEntity(InputEntity):
def _item_initialization(self):
self.valid_value_types = (list, )
self.value_on_not_set = [0, 0, 0, 255]
self.use_alpha = self.schema_data.get("use_alpha", True)
self.value_on_not_set = self.convert_to_valid_type(
self.schema_data.get("default", [0, 0, 0, 255])
)
def set_override_state(self, *args, **kwargs):
super(ColorEntity, self).set_override_state(*args, **kwargs)

View file

@ -442,12 +442,16 @@ class TextEntity(InputEntity):
def _item_initialization(self):
self.valid_value_types = (STRING_TYPE, )
self.value_on_not_set = ""
self.value_on_not_set = self.convert_to_valid_type(
self.schema_data.get("default", "")
)
# GUI attributes
self.multiline = self.schema_data.get("multiline", False)
self.placeholder_text = self.schema_data.get("placeholder")
self.value_hints = self.schema_data.get("value_hints") or []
self.minimum_lines_count = (
self.schema_data.get("minimum_lines_count") or 0)
def schema_validations(self):
if self.multiline and self.value_hints:

View file

@ -380,6 +380,7 @@ How output of the schema could look like on save:
- simple text input
- key `"multiline"` allows to enter multiple lines of text (Default: `False`)
- key `"placeholder"` allows to show text inside input when is empty (Default: `None`)
- key `"minimum_lines_count"` allows to define minimum size hint for UI. Can be 0-n lines.
```
{

View file

@ -42,10 +42,19 @@
"nuke-default": "nuke-default"
},
{
"aces_1.0.3": "aces_1.0.3"
"aces_1.0.3": "aces_1.0.3 (12)"
},
{
"aces_1.1": "aces_1.1"
"aces_1.1": "aces_1.1 (12, 13)"
},
{
"aces_1.2": "aces_1.2 (13, 14)"
},
{
"studio-config-v1.0.0_aces-v1.3_ocio-v2.1": "studio-config-v1.0.0_aces-v1.3_ocio-v2.1 (14)"
},
{
"cg-config-v1.0.0_aces-v1.3_ocio-v2.1": "cg-config-v1.0.0_aces-v1.3_ocio-v2.1 (14)"
},
{
"custom": "custom"
@ -93,6 +102,11 @@
"type": "text",
"key": "thumbnailLut",
"label": "Thumbnails"
},
{
"type": "text",
"key": "monitorOutLut",
"label": "Monitor"
}
]
}

View file

@ -10,6 +10,41 @@
"key": "open_workfile_post_initialization",
"label": "Open Workfile Post Initialization"
},
{
"type": "dict",
"key": "explicit_plugins_loading",
"label": "Explicit Plugins Loading",
"collapsible": true,
"is_group": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"key": "plugins_to_load",
"label": "Plugins To Load",
"object_type": {
"type": "dict",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "text",
"key": "name",
"label": "Name"
}
]
}
}
]
},
{
"key": "imageio",
"type": "dict",
@ -151,6 +186,40 @@
}
]
},
{
"type": "dict",
"key": "include_handles",
"collapsible": true,
"label": "Include/Exclude Handles in default playback & render range",
"children": [
{
"key": "include_handles_default",
"label": "Include handles by default",
"type": "boolean"
},
{
"type": "list",
"key": "per_task_type",
"label": "Include/exclude handles by task type",
"use_label_wrap": true,
"object_type": {
"type": "dict",
"children": [
{
"type": "task-types-enum",
"key": "task_type",
"label": "Task types"
},
{
"type": "boolean",
"key": "include_handles",
"label": "Include handles"
}
]
}
}
]
},
{
"type": "schema",
"name": "schema_scriptsmenu"

View file

@ -26,7 +26,7 @@
"type": "list",
"collapsible": true,
"key": "simple_creators",
"label": "Creator plugins",
"label": "Simple Create Plugins",
"use_label_wrap": true,
"collapsible_key": true,
"object_type": {
@ -292,40 +292,48 @@
]
},
{
"key": "create",
"label": "Create plugins",
"type": "dict",
"collapsible": true,
"key": "BatchMovieCreator",
"label": "Batch Movie Creator",
"collapsible_key": true,
"children": [
{
"type": "label",
"label": "Allows to publish multiple video files in one go. <br />Name of matching asset is parsed from file names ('asset.mov', 'asset_v001.mov', 'my_asset_to_publish.mov')"
},
{
"type": "list",
"key": "default_variants",
"label": "Default variants",
"object_type": {
"type": "text"
}
},
{
"type": "list",
"key": "default_tasks",
"label": "Default tasks",
"object_type": {
"type": "text"
}
},
{
"type": "list",
"key": "extensions",
"label": "Extensions",
"use_label_wrap": true,
"type": "dict",
"collapsible": true,
"key": "BatchMovieCreator",
"label": "Batch Movie Creator",
"collapsible_key": true,
"collapsed": false,
"object_type": "text"
"children": [
{
"type": "label",
"label": "Allows to publish multiple video files in one go. <br />Name of matching asset is parsed from file names ('asset.mov', 'asset_v001.mov', 'my_asset_to_publish.mov')"
},
{
"type": "list",
"key": "default_variants",
"label": "Default variants",
"object_type": {
"type": "text"
}
},
{
"type": "list",
"key": "default_tasks",
"label": "Default tasks",
"object_type": {
"type": "text"
}
},
{
"type": "list",
"key": "extensions",
"label": "Extensions",
"use_label_wrap": true,
"collapsible_key": true,
"collapsed": false,
"object_type": "text"
}
]
}
]
},

View file

@ -7,6 +7,8 @@
{
"type": "dict",
"key": "capture_preset",
"label": "DEPRECATED! Please use \"Profiles\" below.",
"collapsed": false,
"children": [
{
"type": "dict",
@ -176,7 +178,7 @@
{ "all": "All Lights"},
{ "selected": "Selected Lights"},
{ "flat": "Flat Lighting"},
{ "nolights": "No Lights"}
{ "none": "No Lights"}
]
},
{
@ -626,6 +628,747 @@
]
}
]
},
{
"type": "list",
"key": "profiles",
"label": "Profiles",
"object_type": {
"type": "dict",
"children": [
{
"key": "task_types",
"label": "Task types",
"type": "task-types-enum"
},
{
"key": "task_names",
"label": "Task names",
"type": "list",
"object_type": "text"
},
{
"key": "subsets",
"label": "Subset names",
"type": "list",
"object_type": "text"
},
{
"type": "splitter"
},
{
"type": "dict",
"key": "capture_preset",
"children": [
{
"type": "dict",
"key": "Codec",
"children": [
{
"type": "label",
"label": "<b>Codec</b>"
},
{
"type": "text",
"key": "compression",
"label": "Encoding",
"default": "png"
},
{
"type": "text",
"key": "format",
"label": "Format",
"default": "image"
},
{
"type": "number",
"key": "quality",
"label": "Quality",
"decimal": 0,
"minimum": 0,
"maximum": 100,
"default": 95
},
{
"type": "splitter"
}
]
},
{
"type": "dict",
"key": "Display Options",
"children": [
{
"type": "label",
"label": "<b>Display Options</b>"
},
{
"type": "boolean",
"key": "override_display",
"label": "Override display options",
"default": true
},
{
"type": "color",
"key": "background",
"label": "Background Color: ",
"default": [125, 125, 125, 255]
},
{
"type": "boolean",
"key": "displayGradient",
"label": "Display background gradient",
"default": true
},
{
"type": "color",
"key": "backgroundBottom",
"label": "Background Bottom: ",
"default": [125, 125, 125, 255]
},
{
"type": "color",
"key": "backgroundTop",
"label": "Background Top: ",
"default": [125, 125, 125, 255]
}
]
},
{
"type": "splitter"
},
{
"type": "dict",
"key": "Generic",
"children": [
{
"type": "label",
"label": "<b>Generic</b>"
},
{
"type": "boolean",
"key": "isolate_view",
"label": " Isolate view",
"default": true
},
{
"type": "boolean",
"key": "off_screen",
"label": " Off Screen",
"default": true
},
{
"type": "boolean",
"key": "pan_zoom",
"label": " 2D Pan/Zoom",
"default": false
}
]
},
{
"type": "splitter"
},
{
"type": "dict",
"key": "Renderer",
"children": [
{
"type": "label",
"label": "<b>Renderer</b>"
},
{
"type": "enum",
"key": "rendererName",
"label": "Renderer name",
"enum_items": [
{ "vp2Renderer": "Viewport 2.0" }
],
"default": "vp2Renderer"
}
]
},
{
"type": "dict",
"key": "Resolution",
"children": [
{
"type": "splitter"
},
{
"type": "label",
"label": "<b>Resolution</b>"
},
{
"type": "number",
"key": "width",
"label": " Width",
"decimal": 0,
"minimum": 0,
"maximum": 99999,
"default": 0
},
{
"type": "number",
"key": "height",
"label": "Height",
"decimal": 0,
"minimum": 0,
"maximum": 99999,
"default": 0
}
]
},
{
"type": "splitter"
},
{
"type": "dict",
"collapsible": true,
"key": "Viewport Options",
"label": "Viewport Options",
"children": [
{
"type": "boolean",
"key": "override_viewport_options",
"label": "Override Viewport Options",
"default": true
},
{
"type": "enum",
"key": "displayLights",
"label": "Display Lights",
"enum_items": [
{ "default": "Default Lighting"},
{ "all": "All Lights"},
{ "selected": "Selected Lights"},
{ "flat": "Flat Lighting"},
{ "nolights": "No Lights"}
],
"default": "default"
},
{
"type": "boolean",
"key": "displayTextures",
"label": "Display Textures",
"default": true
},
{
"type": "number",
"key": "textureMaxResolution",
"label": "Texture Clamp Resolution",
"decimal": 0,
"default": 1024
},
{
"type": "splitter"
},
{
"type": "label",
"label": "<b>Display</b>"
},
{
"type":"boolean",
"key": "renderDepthOfField",
"label": "Depth of Field",
"default": true
},
{
"type": "splitter"
},
{
"type": "boolean",
"key": "shadows",
"label": "Display Shadows",
"default": true
},
{
"type": "boolean",
"key": "twoSidedLighting",
"label": "Two Sided Lighting",
"default": true
},
{
"type": "splitter"
},
{
"type": "boolean",
"key": "lineAAEnable",
"label": "Enable Anti-Aliasing",
"default": true
},
{
"type": "number",
"key": "multiSample",
"label": "Anti Aliasing Samples",
"decimal": 0,
"minimum": 0,
"maximum": 32,
"default": 8
},
{
"type": "splitter"
},
{
"type": "boolean",
"key": "useDefaultMaterial",
"label": "Use Default Material",
"default": false
},
{
"type": "boolean",
"key": "wireframeOnShaded",
"label": "Wireframe On Shaded",
"default": false
},
{
"type": "boolean",
"key": "xray",
"label": "X-Ray",
"default": false
},
{
"type": "boolean",
"key": "jointXray",
"label": "X-Ray Joints",
"default": false
},
{
"type": "boolean",
"key": "backfaceCulling",
"label": "Backface Culling",
"default": false
},
{
"type": "boolean",
"key": "ssaoEnable",
"label": "Screen Space Ambient Occlusion",
"default": false
},
{
"type": "number",
"key": "ssaoAmount",
"label": "SSAO Amount",
"default": 1
},
{
"type": "number",
"key": "ssaoRadius",
"label": "SSAO Radius",
"default": 16
},
{
"type": "number",
"key": "ssaoFilterRadius",
"label": "SSAO Filter Radius",
"decimal": 0,
"minimum": 1,
"maximum": 32,
"default": 16
},
{
"type": "number",
"key": "ssaoSamples",
"label": "SSAO Samples",
"decimal": 0,
"minimum": 8,
"maximum": 32,
"default": 16
},
{
"type": "splitter"
},
{
"type": "boolean",
"key": "fogging",
"label": "Enable Hardware Fog",
"default": false
},
{
"type": "enum",
"key": "hwFogFalloff",
"label": "Hardware Falloff",
"enum_items": [
{ "0": "Linear"},
{ "1": "Exponential"},
{ "2": "Exponential Squared"}
],
"default": "0"
},
{
"type": "number",
"key": "hwFogDensity",
"label": "Fog Density",
"decimal": 2,
"minimum": 0,
"maximum": 1,
"default": 0
},
{
"type": "number",
"key": "hwFogStart",
"label": "Fog Start",
"default": 0
},
{
"type": "number",
"key": "hwFogEnd",
"label": "Fog End",
"default": 100
},
{
"type": "number",
"key": "hwFogAlpha",
"label": "Fog Alpha",
"default": 0
},
{
"type": "number",
"key": "hwFogColorR",
"label": "Fog Color R",
"decimal": 2,
"minimum": 0,
"maximum": 1,
"default": 1
},
{
"type": "number",
"key": "hwFogColorG",
"label": "Fog Color G",
"decimal": 2,
"minimum": 0,
"maximum": 1,
"default": 1
},
{
"type": "number",
"key": "hwFogColorB",
"label": "Fog Color B",
"decimal": 2,
"minimum": 0,
"maximum": 1,
"default": 1
},
{
"type": "splitter"
},
{
"type": "boolean",
"key": "motionBlurEnable",
"label": "Enable Motion Blur",
"default": false
},
{
"type": "number",
"key": "motionBlurSampleCount",
"label": "Motion Blur Sample Count",
"decimal": 0,
"minimum": 8,
"maximum": 32,
"default": 8
},
{
"type": "number",
"key": "motionBlurShutterOpenFraction",
"label": "Shutter Open Fraction",
"decimal": 3,
"minimum": 0.01,
"maximum": 32,
"default": 0.2
},
{
"type": "splitter"
},
{
"type": "label",
"label": "<b>Show</b>"
},
{
"type": "boolean",
"key": "cameras",
"label": "Cameras",
"default": false
},
{
"type": "boolean",
"key": "clipGhosts",
"label": "Clip Ghosts",
"default": false
},
{
"type": "boolean",
"key": "deformers",
"label": "Deformers",
"default": false
},
{
"type": "boolean",
"key": "dimensions",
"label": "Dimensions",
"default": false
},
{
"type": "boolean",
"key": "dynamicConstraints",
"label": "Dynamic Constraints",
"default": false
},
{
"type": "boolean",
"key": "dynamics",
"label": "Dynamics",
"default": false
},
{
"type": "boolean",
"key": "fluids",
"label": "Fluids",
"default": false
},
{
"type": "boolean",
"key": "follicles",
"label": "Follicles",
"default": false
},
{
"type": "boolean",
"key": "greasePencils",
"label": "Grease Pencil",
"default": false
},
{
"type": "boolean",
"key": "grid",
"label": "Grid",
"default": false
},
{
"type": "boolean",
"key": "hairSystems",
"label": "Hair Systems",
"default": true
},
{
"type": "boolean",
"key": "handles",
"label": "Handles",
"default": false
},
{
"type": "boolean",
"key": "headsUpDisplay",
"label": "HUD",
"default": false
},
{
"type": "boolean",
"key": "ikHandles",
"label": "IK Handles",
"default": false
},
{
"type": "boolean",
"key": "imagePlane",
"label": "Image Planes",
"default": true
},
{
"type": "boolean",
"key": "joints",
"label": "Joints",
"default": false
},
{
"type": "boolean",
"key": "lights",
"label": "Lights",
"default": false
},
{
"type": "boolean",
"key": "locators",
"label": "Locators",
"default": false
},
{
"type": "boolean",
"key": "manipulators",
"label": "Manipulators",
"default": false
},
{
"type": "boolean",
"key": "motionTrails",
"label": "Motion Trails",
"default": false
},
{
"type": "boolean",
"key": "nCloths",
"label": "nCloths",
"default": false
},
{
"type": "boolean",
"key": "nParticles",
"label": "nParticles",
"default": false
},
{
"type": "boolean",
"key": "nRigids",
"label": "nRigids",
"default": false
},
{
"type": "boolean",
"key": "controlVertices",
"label": "NURBS CVs",
"default": false
},
{
"type": "boolean",
"key": "nurbsCurves",
"label": "NURBS Curves",
"default": false
},
{
"type": "boolean",
"key": "hulls",
"label": "NURBS Hulls",
"default": false
},
{
"type": "boolean",
"key": "nurbsSurfaces",
"label": "NURBS Surfaces",
"default": false
},
{
"type": "boolean",
"key": "particleInstancers",
"label": "Particle Instancers",
"default": false
},
{
"type": "boolean",
"key": "pivots",
"label": "Pivots",
"default": false
},
{
"type": "boolean",
"key": "planes",
"label": "Planes",
"default": false
},
{
"type": "boolean",
"key": "pluginShapes",
"label": "Plugin Shapes",
"default": false
},
{
"type": "boolean",
"key": "polymeshes",
"label": "Polygons",
"default": true
},
{
"type": "boolean",
"key": "strokes",
"label": "Strokes",
"default": false
},
{
"type": "boolean",
"key": "subdivSurfaces",
"label": "Subdiv Surfaces",
"default": false
},
{
"type": "boolean",
"key": "textures",
"label": "Texture Placements",
"default": false
},
{
"type": "dict-modifiable",
"key": "pluginObjects",
"label": "Plugin Objects",
"object_type": "boolean"
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "Camera Options",
"label": "Camera Options",
"children": [
{
"type": "boolean",
"key": "displayGateMask",
"label": "Display Gate Mask",
"default": false
},
{
"type": "boolean",
"key": "displayResolution",
"label": "Display Resolution",
"default": false
},
{
"type": "boolean",
"key": "displayFilmGate",
"label": "Display Film Gate",
"default": false
},
{
"type": "boolean",
"key": "displayFieldChart",
"label": "Display Field Chart",
"default": false
},
{
"type": "boolean",
"key": "displaySafeAction",
"label": "Display Safe Action",
"default": false
},
{
"type": "boolean",
"key": "displaySafeTitle",
"label": "Display Safe Title",
"default": false
},
{
"type": "boolean",
"key": "displayFilmPivot",
"label": "Display Film Pivot",
"default": false
},
{
"type": "boolean",
"key": "displayFilmOrigin",
"label": "Display Film Origin",
"default": false
},
{
"type": "number",
"key": "overscan",
"label": "Overscan",
"decimal": 1,
"minimum": 0,
"maximum": 10,
"default": 1
}
]
}
]
}
]
}
}
]
}

View file

@ -91,6 +91,28 @@
"key": "yetiRig"
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "reference_loader",
"label": "Reference Loader",
"children": [
{
"type": "text",
"label": "Namespace",
"key": "namespace"
},
{
"type": "text",
"label": "Group name",
"key": "group_name"
},
{
"type": "label",
"label": "Here's a link to the doc where you can find explanations about customing the naming of referenced assets: https://openpype.io/docs/admin_hosts_maya#load-plugins"
}
]
}
]
}

View file

@ -74,28 +74,34 @@
"nuke-default": "nuke-default"
},
{
"spi-vfx": "spi-vfx"
"spi-vfx": "spi-vfx (11)"
},
{
"spi-anim": "spi-anim"
"spi-anim": "spi-anim (11)"
},
{
"aces_0.1.1": "aces_0.1.1"
"aces_0.1.1": "aces_0.1.1 (11)"
},
{
"aces_0.7.1": "aces_0.7.1"
"aces_0.7.1": "aces_0.7.1 (11)"
},
{
"aces_1.0.1": "aces_1.0.1"
"aces_1.0.1": "aces_1.0.1 (11)"
},
{
"aces_1.0.3": "aces_1.0.3"
"aces_1.0.3": "aces_1.0.3 (11, 12)"
},
{
"aces_1.1": "aces_1.1"
"aces_1.1": "aces_1.1 (12, 13)"
},
{
"aces_1.2": "aces_1.2"
"aces_1.2": "aces_1.2 (13, 14)"
},
{
"studio-config-v1.0.0_aces-v1.3_ocio-v2.1": "studio-config-v1.0.0_aces-v1.3_ocio-v2.1 (14)"
},
{
"cg-config-v1.0.0_aces-v1.3_ocio-v2.1": "cg-config-v1.0.0_aces-v1.3_ocio-v2.1 (14)"
},
{
"custom": "custom"
@ -257,4 +263,4 @@
]
}
]
}
}

View file

@ -30,12 +30,7 @@
"children": [
{
"type": "schema_template",
"name": "template_host_variant_items",
"skip_paths": [
"executables",
"separator",
"arguments"
]
"name": "template_host_variant_items"
}
]
}

View file

@ -6,6 +6,7 @@ import collections
import uuid
import tempfile
import shutil
import inspect
from abc import ABCMeta, abstractmethod
import six
@ -26,8 +27,8 @@ from openpype.pipeline import (
PublishValidationError,
KnownPublishError,
registered_host,
legacy_io,
get_process_id,
OptionalPyblishPluginMixin,
)
from openpype.pipeline.create import (
CreateContext,
@ -2307,6 +2308,37 @@ class PublisherController(BasePublisherController):
def _process_main_thread_item(self, item):
item()
def _is_publish_plugin_active(self, plugin):
"""Decide if publish plugin is active.
This is hack because 'active' is mis-used in mixin
'OptionalPyblishPluginMixin' where 'active' is used for default value
of optional plugins. Because of that is 'active' state of plugin
which inherit from 'OptionalPyblishPluginMixin' ignored. That affects
headless publishing inside host, potentially remote publishing.
We have to change that to match pyblish base, but we can do that
only when all hosts use Publisher because the change requires
change of settings schemas.
Args:
plugin (pyblish.Plugin): Plugin which should be checked if is
active.
Returns:
bool: Is plugin active.
"""
if plugin.active:
return True
if not plugin.optional:
return False
if OptionalPyblishPluginMixin in inspect.getmro(plugin):
return True
return False
def _publish_iterator(self):
"""Main logic center of publishing.
@ -2315,11 +2347,9 @@ class PublisherController(BasePublisherController):
states of currently processed publish plugin and instance. Also
change state of processed orders like validation order has passed etc.
Also stops publishing if should stop on validation.
QUESTION:
Does validate button still make sense?
Also stops publishing, if should stop on validation.
"""
for idx, plugin in enumerate(self._publish_plugins):
self._publish_progress = idx
@ -2344,6 +2374,11 @@ class PublisherController(BasePublisherController):
# Add plugin to publish report
self._publish_report.add_plugin_iter(plugin, self._publish_context)
# WARNING This is hack fix for optional plugins
if not self._is_publish_plugin_active(plugin):
self._publish_report.set_plugin_skipped()
continue
# Trigger callback that new plugin is going to be processed
plugin_label = plugin.__name__
if hasattr(plugin, "label") and plugin.label:
@ -2450,7 +2485,11 @@ def collect_families_from_instances(instances, only_active=False):
instances(list<pyblish.api.Instance>): List of publish instances from
which are families collected.
only_active(bool): Return families only for active instances.
Returns:
list[str]: Families available on instances.
"""
all_families = set()
for instance in instances:
if only_active:

View file

@ -162,7 +162,8 @@ class PluginsModel(QtGui.QStandardItemModel):
items = []
for plugin_item in plugin_items:
item = QtGui.QStandardItem(plugin_item.label)
label = plugin_item.label or plugin_item.name
item = QtGui.QStandardItem(label)
item.setData(False, ITEM_IS_GROUP_ROLE)
item.setData(plugin_item.label, ITEM_LABEL_ROLE)
item.setData(plugin_item.id, ITEM_ID_ROLE)

View file

@ -360,14 +360,16 @@ class TextWidget(InputWidget):
def _add_inputs_to_layout(self):
multiline = self.entity.multiline
if multiline:
self.input_field = SettingsPlainTextEdit(self.content_widget)
input_field = SettingsPlainTextEdit(self.content_widget)
if self.entity.minimum_lines_count:
input_field.set_minimum_lines(self.entity.minimum_lines_count)
else:
self.input_field = SettingsLineEdit(self.content_widget)
input_field = SettingsLineEdit(self.content_widget)
placeholder_text = self.entity.placeholder_text
if placeholder_text:
self.input_field.setPlaceholderText(placeholder_text)
input_field.setPlaceholderText(placeholder_text)
self.input_field = input_field
self.setFocusProxy(self.input_field)
layout_kwargs = {}

View file

@ -4,7 +4,6 @@ from qtpy import QtWidgets, QtCore, QtGui
import qtawesome
from openpype.client import get_projects
from openpype.pipeline import AvalonMongoDB
from openpype.style import get_objected_colors
from openpype.tools.utils.widgets import ImageButton
from openpype.tools.utils.lib import paint_image_with_color
@ -97,6 +96,7 @@ class CompleterView(QtWidgets.QListView):
# Open the widget unactivated
self.setAttribute(QtCore.Qt.WA_ShowWithoutActivating)
self.setAttribute(QtCore.Qt.WA_NoMouseReplay)
delegate = QtWidgets.QStyledItemDelegate()
self.setItemDelegate(delegate)
@ -241,6 +241,18 @@ class SettingsLineEdit(PlaceholderLineEdit):
if self._completer is not None:
self._completer.set_text_filter(text)
def _completer_should_be_visible(self):
return (
self.isVisible()
and (self.hasFocus() or self._completer.hasFocus())
)
def _show_completer(self):
if self._completer_should_be_visible():
self._focus_timer.start()
self._completer.show()
self._update_completer()
def _update_completer(self):
if self._completer is None or not self._completer.isVisible():
return
@ -249,7 +261,7 @@ class SettingsLineEdit(PlaceholderLineEdit):
self._completer.move(new_point)
def _on_focus_timer(self):
if not self.hasFocus() and not self._completer.hasFocus():
if not self._completer_should_be_visible():
self._completer.hide()
self._focus_timer.stop()
@ -258,9 +270,7 @@ class SettingsLineEdit(PlaceholderLineEdit):
self.focused_in.emit()
if self._completer is not None:
self._focus_timer.start()
self._completer.show()
self._update_completer()
self._show_completer()
def paintEvent(self, event):
super(SettingsLineEdit, self).paintEvent(event)
@ -300,11 +310,32 @@ class SettingsLineEdit(PlaceholderLineEdit):
class SettingsPlainTextEdit(QtWidgets.QPlainTextEdit):
focused_in = QtCore.Signal()
_min_lines = 0
def focusInEvent(self, event):
super(SettingsPlainTextEdit, self).focusInEvent(event)
self.focused_in.emit()
def set_minimum_lines(self, lines):
self._min_lines = lines
self.update()
def minimumSizeHint(self):
result = super(SettingsPlainTextEdit, self).minimumSizeHint()
if self._min_lines < 1:
return result
document = self.document()
margins = self.contentsMargins()
d_margin = (
((document.documentMargin() + self.frameWidth()) * 2)
+ margins.top() + margins.bottom()
)
font = document.defaultFont()
font_metrics = QtGui.QFontMetrics(font)
result.setHeight(
d_margin + (font_metrics.lineSpacing() * self._min_lines))
return result
class SettingsToolBtn(ImageButton):
_mask_pixmap = None

View file

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring Pype version."""
__version__ = "3.15.4-nightly.3"
__version__ = "3.15.4"

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "OpenPype"
version = "3.15.3" # OpenPype
version = "3.15.4" # OpenPype
description = "Open VFX and Animation pipeline with support."
authors = ["OpenPype Team <info@openpype.io>"]
license = "MIT License"

View file

@ -0,0 +1,36 @@
"""Dummy environment that allows importing Openpype modules and run
tests in parent folder and all subfolders manually from IDE.
This should not get triggered if the tests are running from `runtests` as it
is expected there that environment is handled by OP itself.
This environment should be enough to run simple `BaseTest` where no
external preparation is necessary (eg. no prepared DB, no source files).
These tests might be enough to import and run simple pyblish plugins to
validate logic.
Please be aware that these tests might use values in real databases, so use
`BaseTest` only for logic without side effects or special configuration. For
these there is `tests.lib.testing_classes.ModuleUnitTest` which would setup
proper test DB (but it requires `mongorestore` on the sys.path)
If pyblish plugins require any host dependent communication, it would need
to be mocked.
This setting of env vars is necessary to run before any imports of OP code!
(This is why it is in `conftest.py` file.)
If your test requires any additional env var, copy this file to folder of your
test, it should only that folder.
"""
import os
if not os.environ.get("IS_TEST"): # running tests from cmd or CI
os.environ["OPENPYPE_MONGO"] = "mongodb://localhost:27017"
os.environ["AVALON_DB"] = "avalon"
os.environ["OPENPYPE_DATABASE_NAME"] = "openpype"
os.environ["AVALON_TIMEOUT"] = '3000'
os.environ["OPENPYPE_DEBUG"] = "1"
os.environ["AVALON_ASSET"] = "test_asset"
os.environ["AVALON_PROJECT"] = "test_project"

View file

@ -0,0 +1,184 @@
"""Test Publish_plugins pipeline publish modul, tests API methods
File:
creates temporary directory and downloads .zip file from GDrive
unzips .zip file
uses content of .zip file (MongoDB's dumps) to import to new databases
with use of 'monkeypatch_session' modifies required env vars
temporarily
runs battery of tests checking that site operation for Sync Server
module are working
removes temporary folder
removes temporary databases (?)
"""
import pytest
import logging
from pyblish.api import Instance as PyblishInstance
from tests.lib.testing_classes import BaseTest
from openpype.plugins.publish.validate_sequence_frames import (
ValidateSequenceFrames
)
log = logging.getLogger(__name__)
class TestValidateSequenceFrames(BaseTest):
""" Testing ValidateSequenceFrames plugin
"""
@pytest.fixture
def instance(self):
class Instance(PyblishInstance):
data = {
"frameStart": 1001,
"frameEnd": 1002,
"representations": []
}
yield Instance
@pytest.fixture(scope="module")
def plugin(self):
plugin = ValidateSequenceFrames()
plugin.log = log
yield plugin
def test_validate_sequence_frames_single_frame(self, instance, plugin):
representations = [
{
"ext": "exr",
"files": "Main_beauty.1001.exr",
}
]
instance.data["representations"] = representations
instance.data["frameEnd"] = 1001
plugin.process(instance)
@pytest.mark.parametrize("files",
[
["Main_beauty.v001.1001.exr",
"Main_beauty.v001.1002.exr"],
["Main_beauty_v001.1001.exr",
"Main_beauty_v001.1002.exr"],
["Main_beauty.1001.1001.exr",
"Main_beauty.1001.1002.exr"],
["Main_beauty_v001_1001.exr",
"Main_beauty_v001_1002.exr"]])
def test_validate_sequence_frames_name(self, instance,
plugin, files):
# tests for names with number inside, caused clique failure before
representations = [
{
"ext": "exr",
"files": files,
}
]
instance.data["representations"] = representations
plugin.process(instance)
@pytest.mark.parametrize("files",
[["Main_beauty.1001.v001.exr",
"Main_beauty.1002.v001.exr"]])
def test_validate_sequence_frames_wrong_name(self, instance,
plugin, files):
# tests for names with number inside, caused clique failure before
representations = [
{
"ext": "exr",
"files": files,
}
]
instance.data["representations"] = representations
with pytest.raises(AssertionError) as excinfo:
plugin.process(instance)
assert ("Must detect single collection" in
str(excinfo.value))
@pytest.mark.parametrize("files",
[["Main_beauty.v001.1001.ass.gz",
"Main_beauty.v001.1002.ass.gz"]])
def test_validate_sequence_frames_possible_wrong_name(
self, instance, plugin, files):
# currently pattern fails on extensions with dots
representations = [
{
"files": files,
}
]
instance.data["representations"] = representations
with pytest.raises(AssertionError) as excinfo:
plugin.process(instance)
assert ("Must not have remainder" in
str(excinfo.value))
@pytest.mark.parametrize("files",
[["Main_beauty.v001.1001.ass.gz",
"Main_beauty.v001.1002.ass.gz"]])
def test_validate_sequence_frames__correct_ext(
self, instance, plugin, files):
# currently pattern fails on extensions with dots
representations = [
{
"ext": "ass.gz",
"files": files,
}
]
instance.data["representations"] = representations
plugin.process(instance)
def test_validate_sequence_frames_multi_frame(self, instance, plugin):
representations = [
{
"ext": "exr",
"files": ["Main_beauty.1001.exr", "Main_beauty.1002.exr",
"Main_beauty.1003.exr"]
}
]
instance.data["representations"] = representations
instance.data["frameEnd"] = 1003
plugin.process(instance)
def test_validate_sequence_frames_multi_frame_missing(self, instance,
plugin):
representations = [
{
"ext": "exr",
"files": ["Main_beauty.1001.exr", "Main_beauty.1002.exr"]
}
]
instance.data["representations"] = representations
instance.data["frameEnd"] = 1003
with pytest.raises(ValueError) as excinfo:
plugin.process(instance)
assert ("Invalid frame range: (1001, 1002) - expected: (1001, 1003)" in
str(excinfo.value))
def test_validate_sequence_frames_multi_frame_hole(self, instance, plugin):
representations = [
{
"ext": "exr",
"files": ["Main_beauty.1001.exr", "Main_beauty.1003.exr"]
}
]
instance.data["representations"] = representations
instance.data["frameEnd"] = 1003
with pytest.raises(AssertionError) as excinfo:
plugin.process(instance)
assert ("Missing frames: [1002]" in str(excinfo.value))
test_case = TestValidateSequenceFrames()

View file

@ -106,6 +106,37 @@ or Deadlines **Draft Tile Assembler**.
This is useful to fix some specific renderer glitches and advanced hacking of Maya Scene files. `Patch name` is label for patch for easier orientation.
`Patch regex` is regex used to find line in file, after `Patch line` string is inserted. Note that you need to add line ending.
## Load Plugins
### Reference Loader
#### Namespace and Group Name
Here you can create your own custom naming for the reference loader.
The custom naming is split into two parts: namespace and group name. If you don't set the namespace or the group name, an error will occur.
Here's the different variables you can use:
<div class="row markdown">
<div class="col col--5 markdown">
| Token | Description |
|---|---|
|`{asset_name}` | Asset name |
|`{asset_type}` | Asset type |
|`{subset}` | Subset name |
|`{family}` | Subset family |
</div>
</div>
The namespace field can contain a single group of '#' number tokens to indicate where the namespace's unique index should go. The amount of tokens defines the zero padding of the number, e.g ### turns into 001.
Warning: Note that a namespace will always be prefixed with a _ if it starts with a digit.
Example:
![Namespace and Group Name](assets/maya-admin_custom_namespace.png)
### Extract GPU Cache
![Maya GPU Cache](assets/maya-admin_gpu_cache.png)
@ -169,6 +200,17 @@ Most settings to override in the viewport are self explanatory and can be found
These options are set on the camera shape when publishing the review. They correspond to attributes on the Maya camera shape node.
![Extract Playblast Settings](assets/maya-admin_extract_playblast_settings_camera_options.png)
## Include/exclude handles by task type
You can include or exclude handles, globally or by task type.
The "Include handles by default" defines whether by default handles are included. Additionally you can add a per task type override whether you want to include or exclude handles.
For example, in this image you can see that handles are included by default in all task types, except for the 'Lighting' task, where the toggle is disabled.
![Include/exclude handles](assets/maya-admin_exclude_handles.png)
And here you can see that the handles are disabled by default, except in 'Animation' task where it's enabled.
![Custom menu definition](assets/maya-admin_include_handles.png)
## Custom Menu
You can add your custom tools menu into Maya by extending definitions in **Maya -> Scripts Menu Definition**.
@ -232,3 +274,14 @@ Fill in the necessary fields (the optional fields are regex filters)
- Build your workfile
![maya build template](assets/maya-build_workfile_from_template.png)
## Explicit Plugins Loading
You can define which plugins to load on launch of Maya here; `project_settings/maya/explicit_plugins_loading`. This can help improve Maya's launch speed, if you know which plugins are needed.
By default only the required plugins are enabled. You can also add any plugin to the list to enable on launch.
:::note technical
When enabling this feature, the workfile will be launched post initialization no matter the setting on `project_settings/maya/open_workfile_post_initialization`. This is to avoid any issues with references needing plugins.
Renderfarm integration is not supported for this feature.
:::

View file

@ -14,17 +14,29 @@ OpenPype has a limitation regarding duplicated names. Name of assets must be uni
### Subset
Usually, an asset needs to be created in multiple *'flavours'*. A character might have multiple different looks, model needs to be published in different resolutions, a standard animation rig might not be usable in a crowd system and so on. 'Subsets' are here to accommodate all this variety that might be needed within a single asset. A model might have subset: *'main'*, *'proxy'*, *'sculpt'*, while data of *'look'* family could have subsets *'main'*, *'dirty'*, *'damaged'*. Subsets have some recommendations for their names, but ultimately it's up to the artist to use them for separation of publishes when needed.
A published output from an asset results in a subset.
The subset type is referred to as [family](#family), for example a rig, pointcache, look.
A single asset can have many subsets, even of a single family, named [variants](#variant).
By default a subset is named as a combination of family + variant, sometimes prefixed with the task name (like workfile).
### Variant
Usually, an asset needs to be created in multiple *'flavours'*. A character might have multiple different looks, model needs to be published in different resolutions, a standard animation rig might not be usable in a crowd system and so on. 'Variants' are here to accommodate all this variety that might be needed within a single asset. A model might have variant: *'main'*, *'proxy'*, *'sculpt'*, while data of *'look'* family could have subsets *'main'*, *'dirty'*, *'damaged'*. Variants have some recommendations for their names, but ultimately it's up to the artist to use them for separation of publishes when needed.
### Version
A numbered iteration of a given subset. Each version contains at least one [representation][daa74ebf].
A numbered iteration of a given subset. Each version contains at least one [representation](#representation).
[daa74ebf]: #representation "representation"
#### Hero version
A hero version is a version that is always the latest published version. When a new publish is generated its written over the previous hero version replacing it in-place as opposed to regular versions where each new publish is a higher version number.
This is an optional feature. The generation of hero versions can be completely disabled in OpenPype by an admin through the Studio Settings.
### Representation
Each published variant can come out of the software in multiple representations. All of them hold exactly the same data, but in different formats. A model, for example, might be saved as `.OBJ`, Alembic, Maya geometry or as all of them, to be ready for pickup in any other applications supporting these formats.
Each published subset version can come out of the software in multiple representations. All of them hold exactly the same data, but in different formats. A model, for example, might be saved as `.OBJ`, Alembic, Maya geometry or as all of them, to be ready for pickup in any other applications supporting these formats.
#### Naming convention
@ -33,18 +45,22 @@ At this moment names of assets, tasks, subsets or representations can contain on
### Family
Each published [subset][3b89d8e0] can have exactly one family assigned to it. Family determines the type of data that the subset holds. Family doesn't dictate the file type, but can enforce certain technical specifications. For example OpenPype default configuration expects `model` family to only contain geometry without any shaders or joints when it is published.
Each published [subset](#subset) can have exactly one family assigned to it. Family determines the type of data that the subset holds. Family doesn't dictate the file type, but can enforce certain technical specifications. For example OpenPype default configuration expects `model` family to only contain geometry without any shaders or joints when it is published.
### Task
[3b89d8e0]: #subset "subset"
A task defines a work area for an asset where an artist can work in. For example asset *characterA* can have tasks named *modeling* and *rigging*. Tasks also have types. Multiple tasks of the same type may exist on an asset. A task with type `fx` could for example appear twice as *fx_fire* and *fx_cloth*.
Without a task you cannot launch a host application.
### Workfile
The source scene file an artist works in within their task. These are versioned scene files and can be loaded and saved (automatically named) through the [workfiles tool](artist_tools_workfiles.md).
### Host
General term for Software or Application supported by OpenPype and Avalon. These are usually DCC applications like Maya, Houdini or Nuke, but can also be a web based service like Ftrack or Clockify.
### Tool
Small piece of software usually dedicated to a particular purpose. Most of OpenPype and Avalon tools have GUI, but some are command line only.
@ -54,6 +70,10 @@ Small piece of software usually dedicated to a particular purpose. Most of OpenP
Process of exporting data from your work scene to versioned, immutable file that can be used by other artists in the studio.
#### (Publish) Instance
A publish instance is a single entry which defines a publish output. Publish instances persist within the workfile. This way we can expect that a publish from a newer workfile will produce similar consistent versioned outputs.
### Load
Process of importing previously published subsets into your current scene, using any of the OpenPype tools.

View file

@ -14,20 +14,29 @@ sidebar_label: Houdini
- [Library Loader](artist_tools_library-loader)
## Publishing Alembic Cameras
You can publish baked camera in Alembic format. Select your camera and go **OpenPype -> Create** and select **Camera (abc)**.
You can publish baked camera in Alembic format.
Select your camera and go **OpenPype -> Create** and select **Camera (abc)**.
This will create Alembic ROP in **out** with path and frame range already set. This node will have a name you've
assigned in the **Creator** menu. For example if you name the subset `Default`, output Alembic Driver will be named
`cameraDefault`. After that, you can **OpenPype -> Publish** and after some validations your camera will be published
to `abc` file.
## Publishing Composites - Image Sequences
You can publish image sequence directly from Houdini. You can use any `cop` network you have and publish image
sequence generated from it. For example I've created simple **cop** graph to generate some noise:
You can publish image sequences directly from Houdini's image COP networks.
You can use any COP node and publish the image sequence generated from it. For example this simple graph to generate some noise:
![Noise COP](assets/houdini_imagesequence_cop.png)
If I want to publish it, I'll select node I like - in this case `radialblur1` and go **OpenPype -> Create** and
select **Composite (Image Sequence)**. This will create `/out/imagesequenceNoise` Composite ROP (I've named my subset
*Noise*) with frame range set. When you hit **Publish** it will render image sequence from selected node.
To publish the output of the `radialblur1` go to **OpenPype -> Create** and
select **Composite (Image Sequence)**. If you name the variant *Noise* this will create the `/out/imagesequenceNoise` Composite ROP with the frame range set.
When you hit **Publish** it will render image sequence from selected node.
:::info Use selection
With *Use selection* is enabled on create the node you have selected when creating will be the node used for published. (It set the Composite ROP node's COP path to it). If you don't do this you'll have to manually set the path as needed on e.g. `/out/imagesequenceNoise` to ensure it outputs what you want.
:::
## Publishing Point Caches (alembic)
Publishing point caches in alembic format is pretty straightforward, but it is by default enforcing better compatibility
@ -46,6 +55,16 @@ you handle `path` attribute is up to you, this is just an example.*
Now select the `output0` node and go **OpenPype -> Create** and select **Point Cache**. It will create
Alembic ROP `/out/pointcacheStrange`
## Publishing Reviews (OpenGL)
To generate a review output from Houdini you need to create a **review** instance.
Go to **OpenPype -> Create** and select **Review**.
![Houdini Create Review](assets/houdini_review_create_attrs.png)
On create, with the **Use Selection** checkbox enabled it will set up the first
camera found in your selection as the camera for the OpenGL ROP node and other
non-cameras are set in **Force Objects**. It will then render those even if
their display flag is disabled in your scene.
## Redshift
:::note Work in progress

View file

@ -43,6 +43,10 @@ Create an Xgen instance to publish. This needs to contain only **one Xgen collec
You can create multiple Xgen instances if you have multiple collections to publish.
:::note
The Xgen publishing requires a namespace on the Xgen collection (palette) and the geometry used.
:::
### Publish
The publishing process will grab geometry used for Xgen along with any external files used in the collection's descriptions. This creates an isolated Maya file with just the Xgen collection's dependencies, so you can use any nested geometry when creating the Xgen description. An Xgen version will consist of:

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -255,7 +255,7 @@ suffix is **"client"** then the final suffix is **"h264_client"**.
| resolution_height | Resolution height. |
| fps | Fps of an output. |
| timecode | Timecode by frame start and fps. |
| focalLength | **Only available in Maya**<br /><br />Camera focal length per frame. Use syntax `{focalLength:.2f}` for decimal truncating. Eg. `35.234985` with `{focalLength:.2f}` would produce `35.23`, whereas `{focalLength:.0f}` would produce `35`. |
| focalLength | **Only available in Maya and Houdini**<br /><br />Camera focal length per frame. Use syntax `{focalLength:.2f}` for decimal truncating. Eg. `35.234985` with `{focalLength:.2f}` would produce `35.23`, whereas `{focalLength:.0f}` would produce `35`. |
:::warning
`timecode` is a specific key that can be **only at the end of content**. (`"BOTTOM_RIGHT": "TC: {timecode}"`)

View file

@ -304,7 +304,7 @@ If source representation has suffix **"h264"** and burnin suffix is **"client"**
| resolution_height | Resolution height. |
| fps | Fps of an output. |
| timecode | Timecode by frame start and fps. |
| focalLength | **Only available in Maya**<br /><br />Camera focal length per frame. Use syntax `{focalLength:.2f}` for decimal truncating. Eg. `35.234985` with `{focalLength:.2f}` would produce `35.23`, whereas `{focalLength:.0f}` would produce `35`. |
| focalLength | **Only available in Maya and Houdini**<br /><br />Camera focal length per frame. Use syntax `{focalLength:.2f}` for decimal truncating. Eg. `35.234985` with `{focalLength:.2f}` would produce `35.23`, whereas `{focalLength:.0f}` would produce `35`. |
:::warning
`timecode` is specific key that can be **only at the end of content**. (`"BOTTOM_RIGHT": "TC: {timecode}"`)