mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
Merge branch 'develop' into bugfix/houdini_creator_settings
This commit is contained in:
commit
07a70d8cf7
59 changed files with 2111 additions and 340 deletions
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -35,6 +35,8 @@ body:
|
|||
label: Version
|
||||
description: What version are you running? Look to OpenPype Tray
|
||||
options:
|
||||
- 3.16.4-nightly.1
|
||||
- 3.16.3
|
||||
- 3.16.3-nightly.5
|
||||
- 3.16.3-nightly.4
|
||||
- 3.16.3-nightly.3
|
||||
|
|
@ -133,8 +135,6 @@ body:
|
|||
- 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
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
|
|
|||
853
CHANGELOG.md
853
CHANGELOG.md
|
|
@ -1,6 +1,841 @@
|
|||
# Changelog
|
||||
|
||||
|
||||
## [3.16.3](https://github.com/ynput/OpenPype/tree/3.16.3)
|
||||
|
||||
|
||||
[Full Changelog](https://github.com/ynput/OpenPype/compare/3.16.2...3.16.3)
|
||||
|
||||
### **🆕 New features**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>AYON: 3rd party addon usage <a href="https://github.com/ynput/OpenPype/pull/5300">#5300</a></summary>
|
||||
|
||||
Prepare OpenPype code to be able use `ayon-third-party` addon which supply ffmpeg and OpenImageIO executables. Because they both can support to define custom arguments (more than one) a new functions were needed to supply.New functions are `get_ffmpeg_tool_args` and `get_oiio_tool_args`. They work similar to previous but instead of string are returning list of strings. All places using previous functions `get_ffmpeg_tool_path` and `get_oiio_tool_path` are now using new ones. They should be backwards compatible and even with addon if returns single argument.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>AYON: Addon settings in OpenPype <a href="https://github.com/ynput/OpenPype/pull/5347">#5347</a></summary>
|
||||
|
||||
Moved settings addons to OpenPype server addon. Modified create package to create zip files for server for each settings addon and for openpype addon.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>AYON: Add folder to template data <a href="https://github.com/ynput/OpenPype/pull/5417">#5417</a></summary>
|
||||
|
||||
Added `folder` to template data, so `{folder[name]}` can be used in templates.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Option to start versioning from 0 <a href="https://github.com/ynput/OpenPype/pull/5262">#5262</a></summary>
|
||||
|
||||
This PR adds a settings option to start all versioning from 0.This PR will replace #4455.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Ayon: deadline implementation <a href="https://github.com/ynput/OpenPype/pull/5321">#5321</a></summary>
|
||||
|
||||
Quick implementation of deadline in Ayon. New Ayon plugin added for Deadline repository
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>AYON: Remove AYON launch logic from OpenPype <a href="https://github.com/ynput/OpenPype/pull/5348">#5348</a></summary>
|
||||
|
||||
Removed AYON launch logic from OpenPype. The logic is outdated at this moment and is replaced by `ayon-launcher`.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
### **🚀 Enhancements**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Bug: Error on multiple instance rig with maya <a href="https://github.com/ynput/OpenPype/pull/5310">#5310</a></summary>
|
||||
|
||||
I change endswith method by startswith method because the set are automacaly name out_SET, out_SET1, out_SET2 ...
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Applications: Use prelaunch hooks to extract environments <a href="https://github.com/ynput/OpenPype/pull/5387">#5387</a></summary>
|
||||
|
||||
Environment variable preparation is based on prelaunch hooks. This should allow to pass OCIO environment variables to farm jobs.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Applications: Launch hooks cleanup <a href="https://github.com/ynput/OpenPype/pull/5395">#5395</a></summary>
|
||||
|
||||
Use `set` instead of `list` for filtering attributes in launch hooks. Celaction hooks dir does not contain `__init__.py`. Celaction prelaunch hook is reusing `CELACTION_ROOT_DIR`. Launch hooks are using full import from `openpype.lib.applications`.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Applications: Environment variables order <a href="https://github.com/ynput/OpenPype/pull/5245">#5245</a></summary>
|
||||
|
||||
Changed order of set environment variables. First are set context environment variables and then project environment overrides. Also asset and task environemnt variables are optional.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Autosave preferences can be read after Nuke opens the script <a href="https://github.com/ynput/OpenPype/pull/5295">#5295</a></summary>
|
||||
|
||||
Looks like I need to open the script in Nuke to be able to correctly load the autosave preferences.This PR reads the Nuke script in context, and offers owerwriting the current script with autosaved one if autosave exists.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Resolve: Update with compatible resolve version and latest docs <a href="https://github.com/ynput/OpenPype/pull/5317">#5317</a></summary>
|
||||
|
||||
Missing information about compatible Resolve version and latest docs from https://github.com/ynput/OpenPype/tree/develop/openpype/hosts/resolve
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Chore: Remove deprecated functions <a href="https://github.com/ynput/OpenPype/pull/5323">#5323</a></summary>
|
||||
|
||||
Removed functions/classes that are deprecated and marked to be removed.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Nuke Render and Prerender nodes Process Order - OP-3555 <a href="https://github.com/ynput/OpenPype/pull/5332">#5332</a></summary>
|
||||
|
||||
This PR exposes control over the order of processing of the instances, by sorting the instances created. The sorting happens on the `render_order` and subset name. If the knob `render_order` is found on the instance, we'll sort by that first before sorting by subset name.`render_order` instances are processed before nodes without `render_order`. This could be extended in the future by querying other knobs but I dont know of a usecase for this.Hardcoded the creator `order` attribute of the `prerender` class to be before the `render`. Could be exposed to the user/studio but dont know of a use case for this.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Unreal: Python Environment Improvements <a href="https://github.com/ynput/OpenPype/pull/5344">#5344</a></summary>
|
||||
|
||||
Automatically set `UE_PYTHONPATH` as `PYTHONPATH` when launching Unreal.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Unreal: Custom location for Unreal Ayon Plugin <a href="https://github.com/ynput/OpenPype/pull/5346">#5346</a></summary>
|
||||
|
||||
Added a new environment variable `AYON_BUILT_UNREAL_PLUGIN` to set an already existing and built Ayon Plugin for Unreal.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Unreal: Better handling of Exceptions in UE Worker threads <a href="https://github.com/ynput/OpenPype/pull/5349">#5349</a></summary>
|
||||
|
||||
Implemented a new `UEWorker` base class to handle exception during the execution of UE Workers.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Houdini: Add farm toggle on creation menu <a href="https://github.com/ynput/OpenPype/pull/5350">#5350</a></summary>
|
||||
|
||||
Deadline Farm publishing and Rendering for Houdini was possible with this PR #4825 farm publishing is enabled by default some ROP nodes which may surprise new users (like me).I think adding a toggle (on by default) on creation UI is better so that users will be aware that there's a farm option for this publish instance.ROPs Modified :
|
||||
- [x] Mantra ROP
|
||||
- [x] Karma ROP
|
||||
- [x] Arnold ROP
|
||||
- [x] Redshift ROP
|
||||
- [x] Vray ROP
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Ftrack: Sync to avalon settings <a href="https://github.com/ynput/OpenPype/pull/5353">#5353</a></summary>
|
||||
|
||||
Added roles settings for sync to avalon action.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Chore: Schemas inside OpenPype <a href="https://github.com/ynput/OpenPype/pull/5354">#5354</a></summary>
|
||||
|
||||
Moved/copied schemas from repository root inside openpype/pipeline.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>AYON: Addons creation enhancements <a href="https://github.com/ynput/OpenPype/pull/5356">#5356</a></summary>
|
||||
|
||||
Enhanced AYON addons creation. Fix issue with `Pattern` typehint. Zip filenames contain version. OpenPype package is skipping modules that are already separated in AYON. Updated settings of addons.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>AYON: Update staging icons <a href="https://github.com/ynput/OpenPype/pull/5372">#5372</a></summary>
|
||||
|
||||
Updated staging icons for staging mode.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Enhancement: Houdini Update pointcache labels <a href="https://github.com/ynput/OpenPype/pull/5373">#5373</a></summary>
|
||||
|
||||
To me it's logical to find pointcaches types listed one after another, but they were named differentlySo, I made this PR to update their labels
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>nuke: split write node product instance features <a href="https://github.com/ynput/OpenPype/pull/5389">#5389</a></summary>
|
||||
|
||||
Improving Write node product instances by allowing precise activation of specific features.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Max: Use the empty modifiers in container to store AYON Parameter <a href="https://github.com/ynput/OpenPype/pull/5396">#5396</a></summary>
|
||||
|
||||
Instead of adding AYON/OP Parameter along with other attributes inside the container, empty modifiers would be created to store AYON/OP custom attributes
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>AfterEffects: Removed unused imports <a href="https://github.com/ynput/OpenPype/pull/5397">#5397</a></summary>
|
||||
|
||||
Removed unused import from extract local render plugin file.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Nuke: adding BBox knob type to settings <a href="https://github.com/ynput/OpenPype/pull/5405">#5405</a></summary>
|
||||
|
||||
Nuke knob types in settings having new `Box` type for reposition nodes like Crop or Reformat.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>SyncServer: Existence of module is optional <a href="https://github.com/ynput/OpenPype/pull/5413">#5413</a></summary>
|
||||
|
||||
Existence of SyncServer module is optional and not required. Added `sync_server` module back to ignored modules when openpype addon is created for AYON. Command `syncserver` is marked as deprecated and redirected to sync server cli.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Webpublisher: Self contain test publish logic <a href="https://github.com/ynput/OpenPype/pull/5414">#5414</a></summary>
|
||||
|
||||
Moved test logic of publishing to webpublisher. Simplified `remote_publish` to remove webpublisher specific logic.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Webpublisher: Cleanup targets <a href="https://github.com/ynput/OpenPype/pull/5418">#5418</a></summary>
|
||||
|
||||
Removed `remote` target from webpublisher and replaced it with 2 targets `webpublisher` and `automated`.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>nuke: update server addon settings with box <a href="https://github.com/ynput/OpenPype/pull/5419">#5419</a></summary>
|
||||
|
||||
updtaing nuke ayon server settings for Box option in knob types.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
### **🐛 Bug fixes**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Maya: fix validate frame range on review attached to other instances <a href="https://github.com/ynput/OpenPype/pull/5296">#5296</a></summary>
|
||||
|
||||
Fixes situation where frame range validator can't be turned off on models if they are attached to reviewable camera in Maya.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Maya: Apply project settings to creators <a href="https://github.com/ynput/OpenPype/pull/5303">#5303</a></summary>
|
||||
|
||||
Project settings were not applied to the creators.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Maya: Validate Model Content <a href="https://github.com/ynput/OpenPype/pull/5336">#5336</a></summary>
|
||||
|
||||
`assemblies` in `cmds.ls` does not seem to work;
|
||||
```python
|
||||
|
||||
from maya import cmds
|
||||
|
||||
|
||||
content_instance = ['|group2|pSphere1_GEO', '|group2|pSphere1_GEO|pSphere1_GEOShape', '|group1|pSphere1_GEO', '|group1|pSphere1_GEO|pSphere1_GEOShape']
|
||||
assemblies = cmds.ls(content_instance, assemblies=True, long=True)
|
||||
print(assemblies)
|
||||
```
|
||||
|
||||
Fixing with string splitting instead.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Bugfix: Maya update defaults variable <a href="https://github.com/ynput/OpenPype/pull/5368">#5368</a></summary>
|
||||
|
||||
So, something was forgotten while moving out from `LegacyCreator` to `NewCreator``LegacyCreator` used `defaults` to list suggested subset names which was changed into `default_variants` in the the `NewCreator`and setting `defaults` to any values has no effect!This update affects:
|
||||
- [x] Model
|
||||
- [x] Set Dress
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Chore: Python 2 support fix <a href="https://github.com/ynput/OpenPype/pull/5375">#5375</a></summary>
|
||||
|
||||
Fix Python 2 support by adding `click` into python 2 dependencies and removing f-string from maya.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Maya: do not create top level group on reference <a href="https://github.com/ynput/OpenPype/pull/5402">#5402</a></summary>
|
||||
|
||||
This PR allows to not wrapping loaded referenced assets in top level group either explicitly for artist or by configuration in Settings.Artists can control group creation in ReferenceLoader options.Default no group creation could be set by emptying `Group Name` in `project_settings/maya/load/reference_loader`
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Settings: Houdini & Maya create plugin settings <a href="https://github.com/ynput/OpenPype/pull/5436">#5436</a></summary>
|
||||
|
||||
Fixes related to Maya and Houdini settings. Renamed `defaults` to `default_variants` in plugin settings to match attribute name on create plugin in both OpenPype and AYON settings. Fixed Houdini AYON settings where were missing settings for defautlt varaints and fixed Maya AYON settings where default factory had wrong assignment.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Maya: Hide CreateAnimation <a href="https://github.com/ynput/OpenPype/pull/5297">#5297</a></summary>
|
||||
|
||||
When converting `animation` family or loading a `rig` family, need to include the `animation` creator but hide it in creator context.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Nuke Anamorphic slate - Read pixel aspect from input <a href="https://github.com/ynput/OpenPype/pull/5304">#5304</a></summary>
|
||||
|
||||
When asset pixel aspect differs from rendered pixel aspect, Nuke slate pixel aspect is not longer taken from asset, but is readed via ffprobe.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Nuke - Allow ExtractReviewDataMov with no timecode knob <a href="https://github.com/ynput/OpenPype/pull/5305">#5305</a></summary>
|
||||
|
||||
ExtractReviewDataMov allows to specify file type. Trying to write some other extension than mov fails on generate_mov assuming that mov64_write_timecode knob exists.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Nuke: removing settings schema with defaults for OpenPype <a href="https://github.com/ynput/OpenPype/pull/5306">#5306</a></summary>
|
||||
|
||||
continuation of https://github.com/ynput/OpenPype/pull/5275
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Bugfix: Dependency without 'inputLinks' not downloaded <a href="https://github.com/ynput/OpenPype/pull/5337">#5337</a></summary>
|
||||
|
||||
Remove condition that avoids downloading dependency without `inputLinks`.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Bugfix: Houdini Creator use selection even if it was toggled off <a href="https://github.com/ynput/OpenPype/pull/5359">#5359</a></summary>
|
||||
|
||||
When creating many product types (families) one after another without refreshing the creator window manually if you toggled `Use selection` once, all the later product types will use selection even if it was toggled offHere's Before it will keep use selection even if it was toggled off, unless you refresh window manuallyhttps://github.com/ynput/OpenPype/assets/20871534/8b890122-5b53-4c6b-897d-6a2f3aa3388aHere's After it works as expectedhttps://github.com/ynput/OpenPype/assets/20871534/6b1db990-de1b-428e-8828-04ab59a44e28
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Houdini: Correct camera selection for karma renderer when using selected node <a href="https://github.com/ynput/OpenPype/pull/5360">#5360</a></summary>
|
||||
|
||||
When user creates the karma rop with selected camera by use selection, it will give the error message of "no render camera found in selection".This PR is to fix the bug of creating karma rop when using selected camera node in Houdini
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>AYON: Environment variables and functions <a href="https://github.com/ynput/OpenPype/pull/5361">#5361</a></summary>
|
||||
|
||||
Prepare code for ayon-launcher compatibility. Fix ayon launcher subprocess calls, added more checks for `AYON_SERVER_ENABLED`, use ayon launcher suitable environment variables in AYON mode and changed outputs of some functions. Replaced usages of `OPENPYPE_REPOS_ROOT` environment variable with `PACKAGE_DIR` variable -> correct paths are used.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Nuke: farm rendering of prerender ignore roots in nuke <a href="https://github.com/ynput/OpenPype/pull/5366">#5366</a></summary>
|
||||
|
||||
`prerender` family was using wrong subset, same as `render` which should be different.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Bugfix: Houdini update defaults variable <a href="https://github.com/ynput/OpenPype/pull/5367">#5367</a></summary>
|
||||
|
||||
So, something was forgotten while moving out from `LegacyCreator` to `NewCreator``LegacyCreator` used `defaults` to list suggested subset names which was changed into `default_variants` in the the `NewCreator`and setting `defaults` to any values has no effect!This update affects:
|
||||
- [x] Arnold ASS
|
||||
- [x] Arnold ROP
|
||||
- [x] Karma ROP
|
||||
- [x] Mantra ROP
|
||||
- [x] Redshift ROP
|
||||
- [x] VRay ROP
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Publisher: Fix create/publish animation <a href="https://github.com/ynput/OpenPype/pull/5369">#5369</a></summary>
|
||||
|
||||
Use geometry movement instead of changing min/max width.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Unreal: Move unreal splash screen to unreal <a href="https://github.com/ynput/OpenPype/pull/5370">#5370</a></summary>
|
||||
|
||||
Moved splash screen code to unreal integration and removed import from Igniter.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Nuke: returned not cleaning of renders folder on the farm <a href="https://github.com/ynput/OpenPype/pull/5374">#5374</a></summary>
|
||||
|
||||
Previous PR enabled explicit cleanup of `renders` folder after farm publishing. This is not matching customer's workflows. Customer wants to have access to files in `renders` folder and potentially redo some frames for long frame sequences.This PR extends logic of marking rendered files for deletion only if instance doesn't have `stagingDir_persistent`.For backwards compatibility all Nuke instances have `stagingDir_persistent` set to True, eg. `renders` folder won't be cleaned after farm publish.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Nuke: loading sequences is working <a href="https://github.com/ynput/OpenPype/pull/5376">#5376</a></summary>
|
||||
|
||||
Loading image sequences was broken after the latest release, version 3.16. However, I am pleased to inform you that it is now functioning as expected.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>AYON: Fix settings conversion for ayon addons <a href="https://github.com/ynput/OpenPype/pull/5377">#5377</a></summary>
|
||||
|
||||
AYON addon settings are available in system settings and does not have available the same values in `"modules"` subkey.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Nuke: OCIO env var workflow <a href="https://github.com/ynput/OpenPype/pull/5379">#5379</a></summary>
|
||||
|
||||
The OCIO environment variable needs to be consistently handled across all platforms. Nuke resolves the custom OCIO config path differently depending on the platform, so we included the ocio config path in the workfile with a partial replacement using an environment variable. Additionally, for Windows sessions, we replaced backward slashes with a TCL expression.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Unreal: Fix Unreal build script <a href="https://github.com/ynput/OpenPype/pull/5381">#5381</a></summary>
|
||||
|
||||
Define 'AYON_UNREAL_ROOT' environment variable in unreal addon.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>3dsMax: Use relative path to MAX_HOST_DIR <a href="https://github.com/ynput/OpenPype/pull/5382">#5382</a></summary>
|
||||
|
||||
Use `MAX_HOST_DIR` to calculate startup script path instead of use relative path to `OPENPYPE_ROOT` environment variable.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Bugfix: Houdini abc validator error message <a href="https://github.com/ynput/OpenPype/pull/5386">#5386</a></summary>
|
||||
|
||||
When ABC path validator fails, it prints node objects not node paths or namesThis bug happened because of updating `get_invalid` method to return nodes instead of node pathsBeforeAfter
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Nuke: node name influence product (subset) name <a href="https://github.com/ynput/OpenPype/pull/5392">#5392</a></summary>
|
||||
|
||||
Nuke now allows users to duplicate publishing instances, making the workflow easier. By duplicating a node and changing its name, users can set the product (subset) name in the publishing context.Users now have the ability to change the variant name in Publisher, which will automatically rename the associated instance node.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Houdini: delete redundant bgeo sop validator <a href="https://github.com/ynput/OpenPype/pull/5394">#5394</a></summary>
|
||||
|
||||
I found out that this `Validate BGEO SOP Path` validator is redundant, it catches two cases that are already implemented in "Validate Output Node". "Validate Output Node" works with `bgeo` as well as `abc` because `"pointcache"` is listed in its families
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Nuke: workfile is not reopening after change of context <a href="https://github.com/ynput/OpenPype/pull/5399">#5399</a></summary>
|
||||
|
||||
Nuke no longer reopens the latest workfile when the context is changed to a different task using the Workfile tool. The issue also affected the Script Clean (from Nuke File menu) and Close feature, but it has now been fixed.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Bugfix: houdini hard coded project settings <a href="https://github.com/ynput/OpenPype/pull/5400">#5400</a></summary>
|
||||
|
||||
I made this PR to solve the issue with hard-coded settings in houdini
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>AYON: 3dsMax settings <a href="https://github.com/ynput/OpenPype/pull/5401">#5401</a></summary>
|
||||
|
||||
Keep `adsk_3dsmax` group in applications settings.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Bugfix: update defaults to default_variants in maya and houdini OP DCC settings <a href="https://github.com/ynput/OpenPype/pull/5407">#5407</a></summary>
|
||||
|
||||
On moving out to new creator in Maya and Houdini updating settings was missed.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Applications: Attributes creation <a href="https://github.com/ynput/OpenPype/pull/5408">#5408</a></summary>
|
||||
|
||||
Applications addon does not cause infinite server restart loop.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Max: fix the bug of handling Object deletion in OP Parameter <a href="https://github.com/ynput/OpenPype/pull/5410">#5410</a></summary>
|
||||
|
||||
If the object is added to the OP parameter and user delete it in the scene thereafter, it will error out the container with OP attributes. This PR resolves the bug.This PR also fixes the bug of not adding the attribute into OP parameter correctly when the user enables "use selections" to link the object into the OP parameter.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Colorspace: including environments from launcher process <a href="https://github.com/ynput/OpenPype/pull/5411">#5411</a></summary>
|
||||
|
||||
Fixed bug in GitHub PR where the OCIO config template was not properly formatting environment variables from System Settings `general/environment`.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Nuke: workfile template fixes <a href="https://github.com/ynput/OpenPype/pull/5428">#5428</a></summary>
|
||||
|
||||
Some bunch of small bugs needed to be fixed
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Houdini, Max: Fix missed function interface change <a href="https://github.com/ynput/OpenPype/pull/5430">#5430</a></summary>
|
||||
|
||||
This PR https://github.com/ynput/OpenPype/pull/5321/files from @kalisp missed updating the `add_render_job_env_var` in Houdini and Max as they are passing an extra arg:
|
||||
```
|
||||
TypeError: add_render_job_env_var() takes 1 positional argument but 2 were given
|
||||
```
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Scene Inventory: Fix issue with 'sync_server' <a href="https://github.com/ynput/OpenPype/pull/5431">#5431</a></summary>
|
||||
|
||||
Fix accesss to `sync_server` attribute in scene inventory.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Unpack project: Fix import issue <a href="https://github.com/ynput/OpenPype/pull/5433">#5433</a></summary>
|
||||
|
||||
Added `load_json_file`, `replace_project_documents` and `store_project_documents` to mongo init.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Chore: Versions post fixes <a href="https://github.com/ynput/OpenPype/pull/5441">#5441</a></summary>
|
||||
|
||||
Fixed issues caused by my fault. Filled right version value to anatomy data.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
### **📃 Testing**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Tests: Copy file_handler as it will be removed by purging ayon code <a href="https://github.com/ynput/OpenPype/pull/5357">#5357</a></summary>
|
||||
|
||||
Ayon code will get purged in the future from this repo/addon, therefore all `ayon_common` will be gone. `file_handler` gets internalized to tests as it is not used anywhere else.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.16.2](https://github.com/ynput/OpenPype/tree/3.16.2)
|
||||
|
||||
|
||||
|
|
@ -357,7 +1192,7 @@ ___
|
|||
|
||||
Add functional base for API Documentation using Sphinx and AutoAPI.
|
||||
|
||||
After unsuccessful #2512, #834 and #210 this is yet another try. But this time without ambition to solve the whole issue. This is making Shinx script to work and nothing else. Any changes and improvements in API docs should be made in subsequent PRs.
|
||||
After unsuccessful #2512, #834 and #210 this is yet another try. But this time without ambition to solve the whole issue. This is making Shinx script to work and nothing else. Any changes and improvements in API docs should be made in subsequent PRs.
|
||||
|
||||
## How to use it
|
||||
|
||||
|
|
@ -368,7 +1203,7 @@ cd .\docs
|
|||
make.bat html
|
||||
```
|
||||
|
||||
or
|
||||
or
|
||||
|
||||
```sh
|
||||
cd ./docs
|
||||
|
|
@ -383,7 +1218,7 @@ During the build you'll see tons of red errors that are pointing to our issues:
|
|||
Invalid import are usually wrong relative imports (too deep) or circular imports.
|
||||
|
||||
2) **Invalid doc-strings**
|
||||
Doc-strings to be processed into documentation needs to follow some syntax - this can be checked by running
|
||||
Doc-strings to be processed into documentation needs to follow some syntax - this can be checked by running
|
||||
`pydocstyle` that is already included with OpenPype
|
||||
3) **Invalid markdown/rst files**
|
||||
md/rst files can be included inside rst files using `.. include::` directive. But they have to be properly formatted.
|
||||
|
|
@ -1570,11 +2405,11 @@ ___
|
|||
<details>
|
||||
<summary>Houdini: Redshift ROP image format bug <a href="https://github.com/ynput/OpenPype/pull/5218">#5218</a></summary>
|
||||
|
||||
Problem :
|
||||
"RS_outputFileFormat" parm value was missing
|
||||
and there were more "image_format" than redshift rop supports
|
||||
Problem :
|
||||
"RS_outputFileFormat" parm value was missing
|
||||
and there were more "image_format" than redshift rop supports
|
||||
|
||||
Fix:
|
||||
Fix:
|
||||
1) removed unnecessary formats from `image_format_enum`
|
||||
2) add the selected format value to `RS_outputFileFormat`
|
||||
___
|
||||
|
|
@ -3751,7 +4586,7 @@ ___
|
|||
<details>
|
||||
<summary>Maya Load References - Add Display Handle Setting <a href="https://github.com/ynput/OpenPype/pull/4904">#4904</a></summary>
|
||||
|
||||
When we load a reference in Maya using OpenPype loader, display handle is checked by default and prevent us to select easily the object in the viewport. I understand that some productions like to keep this option, so I propose to add display handle to the reference loader settings.
|
||||
When we load a reference in Maya using OpenPype loader, display handle is checked by default and prevent us to select easily the object in the viewport. I understand that some productions like to keep this option, so I propose to add display handle to the reference loader settings.
|
||||
|
||||
|
||||
___
|
||||
|
|
@ -3859,7 +4694,7 @@ ___
|
|||
<details>
|
||||
<summary>Patchelf version locked <a href="https://github.com/ynput/OpenPype/pull/4853">#4853</a></summary>
|
||||
|
||||
For Centos dockerfile it is necessary to lock the patchelf version to the older, otherwise the build process fails.
|
||||
For Centos dockerfile it is necessary to lock the patchelf version to the older, otherwise the build process fails.
|
||||
|
||||
___
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ from .mongo import (
|
|||
OpenPypeMongoConnection,
|
||||
get_project_database,
|
||||
get_project_connection,
|
||||
load_json_file,
|
||||
replace_project_documents,
|
||||
store_project_documents,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -17,4 +20,7 @@ __all__ = (
|
|||
"OpenPypeMongoConnection",
|
||||
"get_project_database",
|
||||
"get_project_connection",
|
||||
"load_json_file",
|
||||
"replace_project_documents",
|
||||
"store_project_documents",
|
||||
)
|
||||
|
|
|
|||
229
openpype/client/server/thumbnails.py
Normal file
229
openpype/client/server/thumbnails.py
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
"""Cache of thumbnails downloaded from AYON server.
|
||||
|
||||
Thumbnails are cached to appdirs to predefined directory.
|
||||
|
||||
This should be moved to thumbnails logic in pipeline but because it would
|
||||
overflow OpenPype logic it's here for now.
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import collections
|
||||
|
||||
import appdirs
|
||||
|
||||
FileInfo = collections.namedtuple(
|
||||
"FileInfo",
|
||||
("path", "size", "modification_time")
|
||||
)
|
||||
|
||||
|
||||
class AYONThumbnailCache:
|
||||
"""Cache of thumbnails on local storage.
|
||||
|
||||
Thumbnails are cached to appdirs to predefined directory. Each project has
|
||||
own subfolder with thumbnails -> that's because each project has own
|
||||
thumbnail id validation and file names are thumbnail ids with matching
|
||||
extension. Extensions are predefined (.png and .jpeg).
|
||||
|
||||
Cache has cleanup mechanism which is triggered on initialized by default.
|
||||
|
||||
The cleanup has 2 levels:
|
||||
1. soft cleanup which remove all files that are older then 'days_alive'
|
||||
2. max size cleanup which remove all files until the thumbnails folder
|
||||
contains less then 'max_filesize'
|
||||
- this is time consuming so it's not triggered automatically
|
||||
|
||||
Args:
|
||||
cleanup (bool): Trigger soft cleanup (Cleanup expired thumbnails).
|
||||
"""
|
||||
|
||||
# Lifetime of thumbnails (in seconds)
|
||||
# - default 3 days
|
||||
days_alive = 3
|
||||
# Max size of thumbnail directory (in bytes)
|
||||
# - default 2 Gb
|
||||
max_filesize = 2 * 1024 * 1024 * 1024
|
||||
|
||||
def __init__(self, cleanup=True):
|
||||
self._thumbnails_dir = None
|
||||
self._days_alive_secs = self.days_alive * 24 * 60 * 60
|
||||
if cleanup:
|
||||
self.cleanup()
|
||||
|
||||
def get_thumbnails_dir(self):
|
||||
"""Root directory where thumbnails are stored.
|
||||
|
||||
Returns:
|
||||
str: Path to thumbnails root.
|
||||
"""
|
||||
|
||||
if self._thumbnails_dir is None:
|
||||
# TODO use generic function
|
||||
directory = appdirs.user_data_dir("AYON", "Ynput")
|
||||
self._thumbnails_dir = os.path.join(directory, "thumbnails")
|
||||
return self._thumbnails_dir
|
||||
|
||||
thumbnails_dir = property(get_thumbnails_dir)
|
||||
|
||||
def get_thumbnails_dir_file_info(self):
|
||||
"""Get information about all files in thumbnails directory.
|
||||
|
||||
Returns:
|
||||
List[FileInfo]: List of file information about all files.
|
||||
"""
|
||||
|
||||
thumbnails_dir = self.thumbnails_dir
|
||||
files_info = []
|
||||
if not os.path.exists(thumbnails_dir):
|
||||
return files_info
|
||||
|
||||
for root, _, filenames in os.walk(thumbnails_dir):
|
||||
for filename in filenames:
|
||||
path = os.path.join(root, filename)
|
||||
files_info.append(FileInfo(
|
||||
path, os.path.getsize(path), os.path.getmtime(path)
|
||||
))
|
||||
return files_info
|
||||
|
||||
def get_thumbnails_dir_size(self, files_info=None):
|
||||
"""Got full size of thumbnail directory.
|
||||
|
||||
Args:
|
||||
files_info (List[FileInfo]): Prepared file information about
|
||||
files in thumbnail directory.
|
||||
|
||||
Returns:
|
||||
int: File size of all files in thumbnail directory.
|
||||
"""
|
||||
|
||||
if files_info is None:
|
||||
files_info = self.get_thumbnails_dir_file_info()
|
||||
|
||||
if not files_info:
|
||||
return 0
|
||||
|
||||
return sum(
|
||||
file_info.size
|
||||
for file_info in files_info
|
||||
)
|
||||
|
||||
def cleanup(self, check_max_size=False):
|
||||
"""Cleanup thumbnails directory.
|
||||
|
||||
Args:
|
||||
check_max_size (bool): Also cleanup files to match max size of
|
||||
thumbnails directory.
|
||||
"""
|
||||
|
||||
thumbnails_dir = self.get_thumbnails_dir()
|
||||
# Skip if thumbnails dir does not exists yet
|
||||
if not os.path.exists(thumbnails_dir):
|
||||
return
|
||||
|
||||
self._soft_cleanup(thumbnails_dir)
|
||||
if check_max_size:
|
||||
self._max_size_cleanup(thumbnails_dir)
|
||||
|
||||
def _soft_cleanup(self, thumbnails_dir):
|
||||
current_time = time.time()
|
||||
for root, _, filenames in os.walk(thumbnails_dir):
|
||||
for filename in filenames:
|
||||
path = os.path.join(root, filename)
|
||||
modification_time = os.path.getmtime(path)
|
||||
if current_time - modification_time > self._days_alive_secs:
|
||||
os.remove(path)
|
||||
|
||||
def _max_size_cleanup(self, thumbnails_dir):
|
||||
files_info = self.get_thumbnails_dir_file_info()
|
||||
size = self.get_thumbnails_dir_size(files_info)
|
||||
if size < self.max_filesize:
|
||||
return
|
||||
|
||||
sorted_file_info = collections.deque(
|
||||
sorted(files_info, key=lambda item: item.modification_time)
|
||||
)
|
||||
diff = size - self.max_filesize
|
||||
while diff > 0:
|
||||
if not sorted_file_info:
|
||||
break
|
||||
|
||||
file_info = sorted_file_info.popleft()
|
||||
diff -= file_info.size
|
||||
os.remove(file_info.path)
|
||||
|
||||
def get_thumbnail_filepath(self, project_name, thumbnail_id):
|
||||
"""Get thumbnail by thumbnail id.
|
||||
|
||||
Args:
|
||||
project_name (str): Name of project.
|
||||
thumbnail_id (str): Thumbnail id.
|
||||
|
||||
Returns:
|
||||
Union[str, None]: Path to thumbnail image or None if thumbnail
|
||||
is not cached yet.
|
||||
"""
|
||||
|
||||
if not thumbnail_id:
|
||||
return None
|
||||
|
||||
for ext in (
|
||||
".png",
|
||||
".jpeg",
|
||||
):
|
||||
filepath = os.path.join(
|
||||
self.thumbnails_dir, project_name, thumbnail_id + ext
|
||||
)
|
||||
if os.path.exists(filepath):
|
||||
return filepath
|
||||
return None
|
||||
|
||||
def get_project_dir(self, project_name):
|
||||
"""Path to root directory for specific project.
|
||||
|
||||
Args:
|
||||
project_name (str): Name of project for which root directory path
|
||||
should be returned.
|
||||
|
||||
Returns:
|
||||
str: Path to root of project's thumbnails.
|
||||
"""
|
||||
|
||||
return os.path.join(self.thumbnails_dir, project_name)
|
||||
|
||||
def make_sure_project_dir_exists(self, project_name):
|
||||
project_dir = self.get_project_dir(project_name)
|
||||
if not os.path.exists(project_dir):
|
||||
os.makedirs(project_dir)
|
||||
return project_dir
|
||||
|
||||
def store_thumbnail(self, project_name, thumbnail_id, content, mime_type):
|
||||
"""Store thumbnail to cache folder.
|
||||
|
||||
Args:
|
||||
project_name (str): Project where the thumbnail belong to.
|
||||
thumbnail_id (str): Id of thumbnail.
|
||||
content (bytes): Byte content of thumbnail file.
|
||||
mime_data (str): Type of content.
|
||||
|
||||
Returns:
|
||||
str: Path to cached thumbnail image file.
|
||||
"""
|
||||
|
||||
if mime_type == "image/png":
|
||||
ext = ".png"
|
||||
elif mime_type == "image/jpeg":
|
||||
ext = ".jpeg"
|
||||
else:
|
||||
raise ValueError(
|
||||
"Unknown mime type for thumbnail \"{}\"".format(mime_type))
|
||||
|
||||
project_dir = self.make_sure_project_dir_exists(project_name)
|
||||
thumbnail_path = os.path.join(project_dir, thumbnail_id + ext)
|
||||
with open(thumbnail_path, "wb") as stream:
|
||||
stream.write(content)
|
||||
|
||||
current_time = time.time()
|
||||
os.utime(thumbnail_path, (current_time, current_time))
|
||||
|
||||
return thumbnail_path
|
||||
|
|
@ -28,7 +28,6 @@ class RenderCreator(Creator):
|
|||
create_allow_context_change = True
|
||||
|
||||
# Settings
|
||||
default_variants = []
|
||||
mark_for_review = True
|
||||
|
||||
def create(self, subset_name_from_ui, data, pre_create_data):
|
||||
|
|
@ -171,6 +170,10 @@ class RenderCreator(Creator):
|
|||
)
|
||||
|
||||
self.mark_for_review = plugin_settings["mark_for_review"]
|
||||
self.default_variants = plugin_settings.get(
|
||||
"default_variants",
|
||||
plugin_settings.get("defaults") or []
|
||||
)
|
||||
|
||||
def get_detail_description(self):
|
||||
return """Creator for Render instances
|
||||
|
|
|
|||
|
|
@ -523,6 +523,55 @@ class RenderlayerCreator(NewCreator, MayaCreatorBase):
|
|||
class Loader(LoaderPlugin):
|
||||
hosts = ["maya"]
|
||||
|
||||
def get_custom_namespace_and_group(self, context, options, loader_key):
|
||||
"""Queries Settings to get custom template for namespace and group.
|
||||
|
||||
Group template might be empty >> this forces to not wrap imported items
|
||||
into separate group.
|
||||
|
||||
Args:
|
||||
context (dict)
|
||||
options (dict): artist modifiable options from dialog
|
||||
loader_key (str): key to get separate configuration from Settings
|
||||
('reference_loader'|'import_loader')
|
||||
"""
|
||||
options["attach_to_root"] = True
|
||||
|
||||
asset = context['asset']
|
||||
subset = context['subset']
|
||||
settings = get_project_settings(context['project']['name'])
|
||||
custom_naming = settings['maya']['load'][loader_key]
|
||||
|
||||
if not custom_naming['namespace']:
|
||||
raise LoadError("No namespace specified in "
|
||||
"Maya ReferenceLoader settings")
|
||||
elif not custom_naming['group_name']:
|
||||
self.log.debug("No custom group_name, no group will be created.")
|
||||
options["attach_to_root"] = False
|
||||
|
||||
formatting_data = {
|
||||
"asset_name": asset['name'],
|
||||
"asset_type": asset['type'],
|
||||
"folder": {
|
||||
"name": asset["name"],
|
||||
},
|
||||
"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
|
||||
)
|
||||
|
||||
return custom_group_name, custom_namespace, options
|
||||
|
||||
|
||||
class ReferenceLoader(Loader):
|
||||
"""A basic ReferenceLoader for Maya
|
||||
|
|
@ -565,42 +614,13 @@ class ReferenceLoader(Loader):
|
|||
path = self.filepath_from_context(context)
|
||||
assert os.path.exists(path), "%s does not exist." % path
|
||||
|
||||
asset = context['asset']
|
||||
subset = context['subset']
|
||||
settings = get_project_settings(context['project']['name'])
|
||||
custom_naming = settings['maya']['load']['reference_loader']
|
||||
loaded_containers = []
|
||||
|
||||
if not custom_naming['namespace']:
|
||||
raise LoadError("No namespace specified in "
|
||||
"Maya ReferenceLoader settings")
|
||||
elif not custom_naming['group_name']:
|
||||
self.log.debug("No custom group_name, no group will be created.")
|
||||
options["attach_to_root"] = False
|
||||
|
||||
formatting_data = {
|
||||
"asset_name": asset['name'],
|
||||
"asset_type": asset['type'],
|
||||
"folder": {
|
||||
"name": asset["name"],
|
||||
},
|
||||
"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
|
||||
)
|
||||
custom_group_name, custom_namespace, options = \
|
||||
self.get_custom_namespace_and_group(context, options,
|
||||
"reference_loader")
|
||||
|
||||
count = options.get("count") or 1
|
||||
|
||||
loaded_containers = []
|
||||
for c in range(0, count):
|
||||
namespace = lib.get_custom_namespace(custom_namespace)
|
||||
group_name = "{}:{}".format(
|
||||
|
|
|
|||
|
|
@ -33,6 +33,13 @@ class AbcLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
|
|||
suffix="_abc"
|
||||
)
|
||||
|
||||
attach_to_root = options.get("attach_to_root", True)
|
||||
group_name = options["group_name"]
|
||||
|
||||
# no group shall be created
|
||||
if not attach_to_root:
|
||||
group_name = namespace
|
||||
|
||||
# hero_001 (abc)
|
||||
# asset_counter{optional}
|
||||
path = self.filepath_from_context(context)
|
||||
|
|
@ -41,8 +48,8 @@ class AbcLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
|
|||
nodes = cmds.file(file_url,
|
||||
namespace=namespace,
|
||||
sharedReferenceFile=False,
|
||||
groupReference=True,
|
||||
groupName=options['group_name'],
|
||||
groupReference=attach_to_root,
|
||||
groupName=group_name,
|
||||
reference=True,
|
||||
returnNewNodes=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ import qargparse
|
|||
from openpype.pipeline import load
|
||||
from openpype.hosts.maya.api.lib import (
|
||||
maintained_selection,
|
||||
unique_namespace
|
||||
get_custom_namespace
|
||||
)
|
||||
import openpype.hosts.maya.api.plugin
|
||||
|
||||
|
||||
class SetFrameRangeLoader(load.LoaderPlugin):
|
||||
|
|
@ -83,7 +84,7 @@ class SetFrameRangeWithHandlesLoader(load.LoaderPlugin):
|
|||
animationEndTime=end)
|
||||
|
||||
|
||||
class ImportMayaLoader(load.LoaderPlugin):
|
||||
class ImportMayaLoader(openpype.hosts.maya.api.plugin.Loader):
|
||||
"""Import action for Maya (unmanaged)
|
||||
|
||||
Warning:
|
||||
|
|
@ -130,13 +131,14 @@ class ImportMayaLoader(load.LoaderPlugin):
|
|||
if choice is False:
|
||||
return
|
||||
|
||||
asset = context['asset']
|
||||
custom_group_name, custom_namespace, options = \
|
||||
self.get_custom_namespace_and_group(context, data,
|
||||
"import_loader")
|
||||
|
||||
namespace = namespace or unique_namespace(
|
||||
asset["name"] + "_",
|
||||
prefix="_" if asset["name"][0].isdigit() else "",
|
||||
suffix="_",
|
||||
)
|
||||
namespace = get_custom_namespace(custom_namespace)
|
||||
|
||||
if not options.get("attach_to_root", True):
|
||||
custom_group_name = namespace
|
||||
|
||||
path = self.filepath_from_context(context)
|
||||
with maintained_selection():
|
||||
|
|
@ -145,8 +147,9 @@ class ImportMayaLoader(load.LoaderPlugin):
|
|||
preserveReferences=True,
|
||||
namespace=namespace,
|
||||
returnNewNodes=True,
|
||||
groupReference=True,
|
||||
groupName="{}:{}".format(namespace, name))
|
||||
groupReference=options.get("attach_to_root",
|
||||
True),
|
||||
groupName=custom_group_name)
|
||||
|
||||
if data.get("clean_import", False):
|
||||
remove_attributes = ["cbId"]
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ from openpype.hosts.maya.api.lib import (
|
|||
maintained_selection,
|
||||
get_container_members,
|
||||
parent_nodes,
|
||||
create_rig_animation_instance,
|
||||
get_reference_node
|
||||
create_rig_animation_instance
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -19,8 +19,15 @@ class YetiRigLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
|
|||
def process_reference(
|
||||
self, context, name=None, namespace=None, options=None
|
||||
):
|
||||
group_name = options['group_name']
|
||||
path = self.filepath_from_context(context)
|
||||
|
||||
attach_to_root = options.get("attach_to_root", True)
|
||||
group_name = options["group_name"]
|
||||
|
||||
# no group shall be created
|
||||
if not attach_to_root:
|
||||
group_name = namespace
|
||||
|
||||
with lib.maintained_selection():
|
||||
file_url = self.prepare_root_value(
|
||||
path, context["project"]["name"]
|
||||
|
|
@ -30,7 +37,7 @@ class YetiRigLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
|
|||
namespace=namespace,
|
||||
reference=True,
|
||||
returnNewNodes=True,
|
||||
groupReference=True,
|
||||
groupReference=attach_to_root,
|
||||
groupName=group_name
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -2076,9 +2076,16 @@ class WorkfileSettings(object):
|
|||
str(workfile_settings["OCIO_config"]))
|
||||
|
||||
else:
|
||||
# set values to root
|
||||
# OCIO config path is defined from prelaunch hook
|
||||
self._root_node["colorManagement"].setValue("OCIO")
|
||||
|
||||
# print previous settings in case some were found in workfile
|
||||
residual_path = self._root_node["customOCIOConfigPath"].value()
|
||||
if residual_path:
|
||||
log.info("Residual OCIO config path found: `{}`".format(
|
||||
residual_path
|
||||
))
|
||||
|
||||
# we dont need the key anymore
|
||||
workfile_settings.pop("customOCIOConfigPath", None)
|
||||
workfile_settings.pop("colorManagement", None)
|
||||
|
|
@ -2100,9 +2107,35 @@ class WorkfileSettings(object):
|
|||
|
||||
# set ocio config path
|
||||
if config_data:
|
||||
current_ocio_path = os.getenv("OCIO")
|
||||
if current_ocio_path != config_data["path"]:
|
||||
message = """
|
||||
log.info("OCIO config path found: `{}`".format(
|
||||
config_data["path"]))
|
||||
|
||||
# check if there's a mismatch between environment and settings
|
||||
correct_settings = self._is_settings_matching_environment(
|
||||
config_data)
|
||||
|
||||
# if there's no mismatch between environment and settings
|
||||
if correct_settings:
|
||||
self._set_ocio_config_path_to_workfile(config_data)
|
||||
|
||||
def _is_settings_matching_environment(self, config_data):
|
||||
""" Check if OCIO config path is different from environment
|
||||
|
||||
Args:
|
||||
config_data (dict): OCIO config data from settings
|
||||
|
||||
Returns:
|
||||
bool: True if settings are matching environment, False otherwise
|
||||
"""
|
||||
current_ocio_path = os.environ["OCIO"]
|
||||
settings_ocio_path = config_data["path"]
|
||||
|
||||
# normalize all paths to forward slashes
|
||||
current_ocio_path = current_ocio_path.replace("\\", "/")
|
||||
settings_ocio_path = settings_ocio_path.replace("\\", "/")
|
||||
|
||||
if current_ocio_path != settings_ocio_path:
|
||||
message = """
|
||||
It seems like there's a mismatch between the OCIO config path set in your Nuke
|
||||
settings and the actual path set in your OCIO environment.
|
||||
|
||||
|
|
@ -2120,12 +2153,118 @@ Please note the paths for your reference:
|
|||
|
||||
Reopening Nuke should synchronize these paths and resolve any discrepancies.
|
||||
"""
|
||||
nuke.message(
|
||||
message.format(
|
||||
env_path=current_ocio_path,
|
||||
settings_path=config_data["path"]
|
||||
)
|
||||
nuke.message(
|
||||
message.format(
|
||||
env_path=current_ocio_path,
|
||||
settings_path=settings_ocio_path
|
||||
)
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _set_ocio_config_path_to_workfile(self, config_data):
|
||||
""" Set OCIO config path to workfile
|
||||
|
||||
Path set into nuke workfile. It is trying to replace path with
|
||||
environment variable if possible. If not, it will set it as it is.
|
||||
It also saves the script to apply the change, but only if it's not
|
||||
empty Untitled script.
|
||||
|
||||
Args:
|
||||
config_data (dict): OCIO config data from settings
|
||||
|
||||
"""
|
||||
# replace path with env var if possible
|
||||
ocio_path = self._replace_ocio_path_with_env_var(config_data)
|
||||
|
||||
log.info("Setting OCIO config path to: `{}`".format(
|
||||
ocio_path))
|
||||
|
||||
self._root_node["customOCIOConfigPath"].setValue(
|
||||
ocio_path
|
||||
)
|
||||
self._root_node["OCIO_config"].setValue("custom")
|
||||
|
||||
# only save script if it's not empty
|
||||
if self._root_node["name"].value() != "":
|
||||
log.info("Saving script to apply OCIO config path change.")
|
||||
nuke.scriptSave()
|
||||
|
||||
def _get_included_vars(self, config_template):
|
||||
""" Get all environment variables included in template
|
||||
|
||||
Args:
|
||||
config_template (str): OCIO config template from settings
|
||||
|
||||
Returns:
|
||||
list: list of environment variables included in template
|
||||
"""
|
||||
# resolve all environments for whitelist variables
|
||||
included_vars = [
|
||||
"BUILTIN_OCIO_ROOT",
|
||||
]
|
||||
|
||||
# include all project root related env vars
|
||||
for env_var in os.environ:
|
||||
if env_var.startswith("OPENPYPE_PROJECT_ROOT_"):
|
||||
included_vars.append(env_var)
|
||||
|
||||
# use regex to find env var in template with format {ENV_VAR}
|
||||
# this way we make sure only template used env vars are included
|
||||
env_var_regex = r"\{([A-Z0-9_]+)\}"
|
||||
env_var = re.findall(env_var_regex, config_template)
|
||||
if env_var:
|
||||
included_vars.append(env_var[0])
|
||||
|
||||
return included_vars
|
||||
|
||||
def _replace_ocio_path_with_env_var(self, config_data):
|
||||
""" Replace OCIO config path with environment variable
|
||||
|
||||
Environment variable is added as TCL expression to path. TCL expression
|
||||
is also replacing backward slashes found in path for windows
|
||||
formatted values.
|
||||
|
||||
Args:
|
||||
config_data (str): OCIO config dict from settings
|
||||
|
||||
Returns:
|
||||
str: OCIO config path with environment variable TCL expression
|
||||
"""
|
||||
config_path = config_data["path"]
|
||||
config_template = config_data["template"]
|
||||
|
||||
included_vars = self._get_included_vars(config_template)
|
||||
|
||||
# make sure we return original path if no env var is included
|
||||
new_path = config_path
|
||||
|
||||
for env_var in included_vars:
|
||||
env_path = os.getenv(env_var)
|
||||
if not env_path:
|
||||
continue
|
||||
|
||||
# it has to be directory current process can see
|
||||
if not os.path.isdir(env_path):
|
||||
continue
|
||||
|
||||
# make sure paths are in same format
|
||||
env_path = env_path.replace("\\", "/")
|
||||
path = config_path.replace("\\", "/")
|
||||
|
||||
# check if env_path is in path and replace to first found positive
|
||||
if env_path in path:
|
||||
# with regsub we make sure path format of slashes is correct
|
||||
resub_expr = (
|
||||
"[regsub -all {{\\\\}} [getenv {}] \"/\"]").format(env_var)
|
||||
|
||||
new_path = path.replace(
|
||||
env_path, resub_expr
|
||||
)
|
||||
break
|
||||
|
||||
return new_path
|
||||
|
||||
def set_writes_colorspace(self):
|
||||
''' Adds correct colorspace to write node dict
|
||||
|
|
@ -2239,7 +2378,7 @@ Reopening Nuke should synchronize these paths and resolve any discrepancies.
|
|||
knobs["to"]))
|
||||
|
||||
def set_colorspace(self):
|
||||
''' Setting colorpace following presets
|
||||
''' Setting colorspace following presets
|
||||
'''
|
||||
# get imageio
|
||||
nuke_colorspace = get_nuke_imageio_settings()
|
||||
|
|
@ -2247,17 +2386,16 @@ Reopening Nuke should synchronize these paths and resolve any discrepancies.
|
|||
log.info("Setting colorspace to workfile...")
|
||||
try:
|
||||
self.set_root_colorspace(nuke_colorspace)
|
||||
except AttributeError:
|
||||
msg = "set_colorspace(): missing `workfile` settings in template"
|
||||
except AttributeError as _error:
|
||||
msg = "Set Colorspace to workfile error: {}".format(_error)
|
||||
nuke.message(msg)
|
||||
|
||||
log.info("Setting colorspace to viewers...")
|
||||
try:
|
||||
self.set_viewers_colorspace(nuke_colorspace["viewer"])
|
||||
except AttributeError:
|
||||
msg = "set_colorspace(): missing `viewer` settings in template"
|
||||
except AttributeError as _error:
|
||||
msg = "Set Colorspace to viewer error: {}".format(_error)
|
||||
nuke.message(msg)
|
||||
log.error(msg)
|
||||
|
||||
log.info("Setting colorspace to write nodes...")
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -114,6 +114,11 @@ class NukePlaceholderPlugin(PlaceholderPlugin):
|
|||
placeholder_data[key] = value
|
||||
return placeholder_data
|
||||
|
||||
def delete_placeholder(self, placeholder):
|
||||
"""Remove placeholder if building was successful"""
|
||||
placeholder_node = nuke.toNode(placeholder.scene_identifier)
|
||||
nuke.delete(placeholder_node)
|
||||
|
||||
|
||||
class NukePlaceholderLoadPlugin(NukePlaceholderPlugin, PlaceholderLoadMixin):
|
||||
identifier = "nuke.load"
|
||||
|
|
@ -276,14 +281,6 @@ class NukePlaceholderLoadPlugin(NukePlaceholderPlugin, PlaceholderLoadMixin):
|
|||
placeholder.data["nb_children"] += 1
|
||||
reset_selection()
|
||||
|
||||
# remove placeholders marked as delete
|
||||
if (
|
||||
placeholder.data.get("delete")
|
||||
and not placeholder.data.get("keep_placeholder")
|
||||
):
|
||||
self.log.debug("Deleting node: {}".format(placeholder_node.name()))
|
||||
nuke.delete(placeholder_node)
|
||||
|
||||
# go back to root group
|
||||
nuke.root().begin()
|
||||
|
||||
|
|
@ -690,14 +687,6 @@ class NukePlaceholderCreatePlugin(
|
|||
placeholder.data["nb_children"] += 1
|
||||
reset_selection()
|
||||
|
||||
# remove placeholders marked as delete
|
||||
if (
|
||||
placeholder.data.get("delete")
|
||||
and not placeholder.data.get("keep_placeholder")
|
||||
):
|
||||
self.log.debug("Deleting node: {}".format(placeholder_node.name()))
|
||||
nuke.delete(placeholder_node)
|
||||
|
||||
# go back to root group
|
||||
nuke.root().begin()
|
||||
|
||||
|
|
|
|||
|
|
@ -96,7 +96,8 @@ class LoadImage(load.LoaderPlugin):
|
|||
|
||||
file = file.replace("\\", "/")
|
||||
|
||||
repr_cont = context["representation"]["context"]
|
||||
representation = context["representation"]
|
||||
repr_cont = representation["context"]
|
||||
frame = repr_cont.get("frame")
|
||||
if frame:
|
||||
padding = len(frame)
|
||||
|
|
@ -104,16 +105,7 @@ class LoadImage(load.LoaderPlugin):
|
|||
frame,
|
||||
format(frame_number, "0{}".format(padding)))
|
||||
|
||||
name_data = {
|
||||
"asset": repr_cont["asset"],
|
||||
"subset": repr_cont["subset"],
|
||||
"representation": context["representation"]["name"],
|
||||
"ext": repr_cont["representation"],
|
||||
"id": context["representation"]["_id"],
|
||||
"class_name": self.__class__.__name__
|
||||
}
|
||||
|
||||
read_name = self.node_name_template.format(**name_data)
|
||||
read_name = self._get_node_name(representation)
|
||||
|
||||
# Create the Loader with the filename path set
|
||||
with viewer_update_and_undo_stop():
|
||||
|
|
@ -212,6 +204,8 @@ class LoadImage(load.LoaderPlugin):
|
|||
last = first = int(frame_number)
|
||||
|
||||
# Set the global in to the start frame of the sequence
|
||||
read_name = self._get_node_name(representation)
|
||||
node["name"].setValue(read_name)
|
||||
node["file"].setValue(file)
|
||||
node["origfirst"].setValue(first)
|
||||
node["first"].setValue(first)
|
||||
|
|
@ -250,3 +244,17 @@ class LoadImage(load.LoaderPlugin):
|
|||
|
||||
with viewer_update_and_undo_stop():
|
||||
nuke.delete(node)
|
||||
|
||||
def _get_node_name(self, representation):
|
||||
|
||||
repre_cont = representation["context"]
|
||||
name_data = {
|
||||
"asset": repre_cont["asset"],
|
||||
"subset": repre_cont["subset"],
|
||||
"representation": representation["name"],
|
||||
"ext": repre_cont["representation"],
|
||||
"id": representation["_id"],
|
||||
"class_name": self.__class__.__name__
|
||||
}
|
||||
|
||||
return self.node_name_template.format(**name_data)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ Provides:
|
|||
import pyblish.api
|
||||
|
||||
from openpype.client import get_last_version_by_subset_name
|
||||
from openpype.pipeline.version_start import get_versioning_start
|
||||
|
||||
|
||||
class CollectPublishedVersion(pyblish.api.ContextPlugin):
|
||||
|
|
@ -47,9 +48,17 @@ class CollectPublishedVersion(pyblish.api.ContextPlugin):
|
|||
version_doc = get_last_version_by_subset_name(project_name,
|
||||
workfile_subset_name,
|
||||
asset_id)
|
||||
version_int = 1
|
||||
|
||||
if version_doc:
|
||||
version_int += int(version_doc["name"])
|
||||
version_int = int(version_doc["name"]) + 1
|
||||
else:
|
||||
version_int = get_versioning_start(
|
||||
project_name,
|
||||
"photoshop",
|
||||
task_name=context.data["task"],
|
||||
task_type=context.data["taskType"],
|
||||
project_settings=context.data["project_settings"]
|
||||
)
|
||||
|
||||
self.log.debug(f"Setting {version_int} to context.")
|
||||
context.data["version"] = version_int
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ def get_layers_pre_post_behavior(layer_ids, communicator=None):
|
|||
|
||||
Pre and Post behaviors is enumerator of possible values:
|
||||
- "none"
|
||||
- "repeat" / "loop"
|
||||
- "repeat"
|
||||
- "pingpong"
|
||||
- "hold"
|
||||
|
||||
|
|
@ -242,7 +242,7 @@ def get_layers_pre_post_behavior(layer_ids, communicator=None):
|
|||
{
|
||||
0: {
|
||||
"pre": "none",
|
||||
"post": "loop"
|
||||
"post": "repeat"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -77,13 +77,15 @@ def _calculate_pre_behavior_copy(
|
|||
for frame_idx in range(range_start, layer_frame_start):
|
||||
output_idx_by_frame_idx[frame_idx] = first_exposure_frame
|
||||
|
||||
elif pre_beh in ("loop", "repeat"):
|
||||
elif pre_beh == "repeat":
|
||||
# Loop backwards from last frame of layer
|
||||
for frame_idx in reversed(range(range_start, layer_frame_start)):
|
||||
eq_frame_idx_offset = (
|
||||
(layer_frame_end - frame_idx) % frame_count
|
||||
)
|
||||
eq_frame_idx = layer_frame_end - eq_frame_idx_offset
|
||||
eq_frame_idx = layer_frame_start + (
|
||||
layer_frame_end - eq_frame_idx_offset
|
||||
)
|
||||
output_idx_by_frame_idx[frame_idx] = eq_frame_idx
|
||||
|
||||
elif pre_beh == "pingpong":
|
||||
|
|
@ -139,10 +141,10 @@ def _calculate_post_behavior_copy(
|
|||
for frame_idx in range(layer_frame_end + 1, range_end + 1):
|
||||
output_idx_by_frame_idx[frame_idx] = last_exposure_frame
|
||||
|
||||
elif post_beh in ("loop", "repeat"):
|
||||
elif post_beh == "repeat":
|
||||
# Loop backwards from last frame of layer
|
||||
for frame_idx in range(layer_frame_end + 1, range_end + 1):
|
||||
eq_frame_idx = frame_idx % frame_count
|
||||
eq_frame_idx = layer_frame_start + (frame_idx % frame_count)
|
||||
output_idx_by_frame_idx[frame_idx] = eq_frame_idx
|
||||
|
||||
elif post_beh == "pingpong":
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from openpype.hosts.tvpaint.api.lib import (
|
|||
from openpype.hosts.tvpaint.api.pipeline import (
|
||||
get_current_workfile_context,
|
||||
)
|
||||
from openpype.pipeline.version_start import get_versioning_start
|
||||
|
||||
|
||||
class LoadWorkfile(plugin.Loader):
|
||||
|
|
@ -95,7 +96,13 @@ class LoadWorkfile(plugin.Loader):
|
|||
)[1]
|
||||
|
||||
if version is None:
|
||||
version = 1
|
||||
version = get_versioning_start(
|
||||
project_name,
|
||||
"tvpaint",
|
||||
task_name=task_name,
|
||||
task_type=data["task"]["type"],
|
||||
family="workfile"
|
||||
)
|
||||
else:
|
||||
version += 1
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ from openpype.lib import (
|
|||
)
|
||||
from openpype.pipeline.create import get_subset_name
|
||||
from openpype_modules.webpublisher.lib import parse_json
|
||||
from openpype.pipeline.version_start import get_versioning_start
|
||||
|
||||
|
||||
class CollectPublishedFiles(pyblish.api.ContextPlugin):
|
||||
|
|
@ -103,7 +104,13 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin):
|
|||
project_settings=context.data["project_settings"]
|
||||
)
|
||||
version = self._get_next_version(
|
||||
project_name, asset_doc, subset_name
|
||||
project_name,
|
||||
asset_doc,
|
||||
task_name,
|
||||
task_type,
|
||||
family,
|
||||
subset_name,
|
||||
context
|
||||
)
|
||||
next_versions.append(version)
|
||||
|
||||
|
|
@ -141,8 +148,9 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin):
|
|||
try:
|
||||
no_of_frames = self._get_number_of_frames(file_url)
|
||||
if no_of_frames:
|
||||
frame_end = int(frame_start) + \
|
||||
math.ceil(no_of_frames)
|
||||
frame_end = (
|
||||
int(frame_start) + math.ceil(no_of_frames)
|
||||
)
|
||||
frame_end = math.ceil(frame_end) - 1
|
||||
instance.data["frameEnd"] = frame_end
|
||||
self.log.debug("frameEnd:: {}".format(
|
||||
|
|
@ -270,7 +278,16 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin):
|
|||
config["families"],
|
||||
config["tags"])
|
||||
|
||||
def _get_next_version(self, project_name, asset_doc, subset_name):
|
||||
def _get_next_version(
|
||||
self,
|
||||
project_name,
|
||||
asset_doc,
|
||||
task_name,
|
||||
task_type,
|
||||
family,
|
||||
subset_name,
|
||||
context
|
||||
):
|
||||
"""Returns version number or 1 for 'asset' and 'subset'"""
|
||||
|
||||
version_doc = get_last_version_by_subset_name(
|
||||
|
|
@ -279,9 +296,19 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin):
|
|||
asset_doc["_id"],
|
||||
fields=["name"]
|
||||
)
|
||||
version = 1
|
||||
if version_doc:
|
||||
version += int(version_doc["name"])
|
||||
version = int(version_doc["name"]) + 1
|
||||
else:
|
||||
version = get_versioning_start(
|
||||
project_name,
|
||||
"webpublisher",
|
||||
task_name=task_name,
|
||||
task_type=task_type,
|
||||
family=family,
|
||||
subset=subset_name,
|
||||
project_settings=context.data["project_settings"]
|
||||
)
|
||||
|
||||
return version
|
||||
|
||||
def _get_number_of_frames(self, file_url):
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import os
|
||||
import json
|
||||
import re
|
||||
from copy import copy, deepcopy
|
||||
from copy import deepcopy
|
||||
import requests
|
||||
import clique
|
||||
|
||||
|
|
@ -16,6 +16,7 @@ from openpype.client import (
|
|||
from openpype.pipeline import publish, legacy_io
|
||||
from openpype.lib import EnumDef, is_running_from_build
|
||||
from openpype.tests.lib import is_in_tests
|
||||
from openpype.pipeline.version_start import get_versioning_start
|
||||
|
||||
from openpype.pipeline.farm.pyblish_functions import (
|
||||
create_skeleton_instance,
|
||||
|
|
@ -566,7 +567,15 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin,
|
|||
if version:
|
||||
version = int(version["name"]) + 1
|
||||
else:
|
||||
version = 1
|
||||
version = get_versioning_start(
|
||||
project_name,
|
||||
template_data["app"],
|
||||
task_name=template_data["task"]["name"],
|
||||
task_type=template_data["task"]["type"],
|
||||
family="render",
|
||||
subset=subset,
|
||||
project_settings=context.data["project_settings"]
|
||||
)
|
||||
|
||||
host_name = context.data["hostName"]
|
||||
task_info = template_data.get("task") or {}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@ Provides:
|
|||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import collections
|
||||
|
||||
import six
|
||||
import pyblish.api
|
||||
import clique
|
||||
|
||||
|
|
|
|||
|
|
@ -116,6 +116,18 @@ class CopyLastPublishedWorkfile(PreLaunchHook):
|
|||
"task": {"name": task_name, "type": task_type}
|
||||
}
|
||||
|
||||
# Add version filter
|
||||
workfile_version = self.launch_context.data.get("workfile_version", -1)
|
||||
if workfile_version > 0 and workfile_version not in {None, "last"}:
|
||||
context_filters["version"] = self.launch_context.data[
|
||||
"workfile_version"
|
||||
]
|
||||
|
||||
# Only one version will be matched
|
||||
version_index = 0
|
||||
else:
|
||||
version_index = workfile_version
|
||||
|
||||
workfile_representations = list(get_representations(
|
||||
project_name,
|
||||
context_filters=context_filters
|
||||
|
|
@ -133,9 +145,10 @@ class CopyLastPublishedWorkfile(PreLaunchHook):
|
|||
lambda r: r["context"].get("version") is not None,
|
||||
workfile_representations
|
||||
)
|
||||
workfile_representation = max(
|
||||
# Get workfile version
|
||||
workfile_representation = sorted(
|
||||
filtered_repres, key=lambda r: r["context"]["version"]
|
||||
)
|
||||
)[version_index]
|
||||
|
||||
# Copy file and substitute path
|
||||
last_published_workfile_path = download_last_published_workfile(
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ from .context_tools import (
|
|||
get_current_host_name,
|
||||
get_current_project_name,
|
||||
get_current_asset_name,
|
||||
get_current_task_name,
|
||||
get_current_task_name
|
||||
)
|
||||
install = install_host
|
||||
uninstall = uninstall_host
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from openpype.client import (
|
|||
from openpype.lib.events import emit_event
|
||||
from openpype.modules import load_modules, ModulesManager
|
||||
from openpype.settings import get_project_settings
|
||||
from openpype.tests.lib import is_in_tests
|
||||
|
||||
from .publish.lib import filter_pyblish_plugins
|
||||
from .anatomy import Anatomy
|
||||
|
|
@ -35,7 +36,7 @@ from . import (
|
|||
register_inventory_action_path,
|
||||
register_creator_plugin_path,
|
||||
deregister_loader_plugin_path,
|
||||
deregister_inventory_action_path,
|
||||
deregister_inventory_action_path
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -142,6 +143,10 @@ def install_host(host):
|
|||
else:
|
||||
pyblish.api.register_target("local")
|
||||
|
||||
if is_in_tests():
|
||||
print("Registering pyblish target: automated")
|
||||
pyblish.api.register_target("automated")
|
||||
|
||||
project_name = os.environ.get("AVALON_PROJECT")
|
||||
host_name = os.environ.get("AVALON_APP")
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from .constants import (
|
|||
SUBSET_NAME_ALLOWED_SYMBOLS,
|
||||
DEFAULT_SUBSET_TEMPLATE,
|
||||
PRE_CREATE_THUMBNAIL_KEY,
|
||||
DEFAULT_VARIANT_VALUE,
|
||||
)
|
||||
|
||||
from .utils import (
|
||||
|
|
@ -50,6 +51,7 @@ __all__ = (
|
|||
"SUBSET_NAME_ALLOWED_SYMBOLS",
|
||||
"DEFAULT_SUBSET_TEMPLATE",
|
||||
"PRE_CREATE_THUMBNAIL_KEY",
|
||||
"DEFAULT_VARIANT_VALUE",
|
||||
|
||||
"get_last_versions_for_instances",
|
||||
"get_next_versions_for_instances",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
SUBSET_NAME_ALLOWED_SYMBOLS = "a-zA-Z0-9_."
|
||||
DEFAULT_SUBSET_TEMPLATE = "{family}{Variant}"
|
||||
PRE_CREATE_THUMBNAIL_KEY = "thumbnail_source"
|
||||
DEFAULT_VARIANT_VALUE = "Main"
|
||||
|
||||
|
||||
__all__ = (
|
||||
"SUBSET_NAME_ALLOWED_SYMBOLS",
|
||||
"DEFAULT_SUBSET_TEMPLATE",
|
||||
"PRE_CREATE_THUMBNAIL_KEY",
|
||||
"DEFAULT_VARIANT_VALUE",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import os
|
||||
import copy
|
||||
import collections
|
||||
|
||||
|
|
@ -20,6 +19,7 @@ from openpype.pipeline.plugin_discover import (
|
|||
deregister_plugin_path
|
||||
)
|
||||
|
||||
from .constants import DEFAULT_VARIANT_VALUE
|
||||
from .subset_name import get_subset_name
|
||||
from .utils import get_next_versions_for_instances
|
||||
from .legacy_create import LegacyCreator
|
||||
|
|
@ -517,7 +517,7 @@ class Creator(BaseCreator):
|
|||
default_variants = []
|
||||
|
||||
# Default variant used in 'get_default_variant'
|
||||
default_variant = None
|
||||
_default_variant = None
|
||||
|
||||
# Short description of family
|
||||
# - may not be used if `get_description` is overriden
|
||||
|
|
@ -543,6 +543,21 @@ class Creator(BaseCreator):
|
|||
# - similar to instance attribute definitions
|
||||
pre_create_attr_defs = []
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
cls = self.__class__
|
||||
|
||||
# Fix backwards compatibility for plugins which override
|
||||
# 'default_variant' attribute directly
|
||||
if not isinstance(cls.default_variant, property):
|
||||
# Move value from 'default_variant' to '_default_variant'
|
||||
self._default_variant = self.default_variant
|
||||
# Create property 'default_variant' on the class
|
||||
cls.default_variant = property(
|
||||
cls._get_default_variant_wrap,
|
||||
cls._set_default_variant_wrap
|
||||
)
|
||||
super(Creator, self).__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def show_order(self):
|
||||
"""Order in which is creator shown in UI.
|
||||
|
|
@ -595,10 +610,10 @@ class Creator(BaseCreator):
|
|||
def get_default_variants(self):
|
||||
"""Default variant values for UI tooltips.
|
||||
|
||||
Replacement of `defatults` attribute. Using method gives ability to
|
||||
have some "logic" other than attribute values.
|
||||
Replacement of `default_variants` attribute. Using method gives
|
||||
ability to have some "logic" other than attribute values.
|
||||
|
||||
By default returns `default_variants` value.
|
||||
By default, returns `default_variants` value.
|
||||
|
||||
Returns:
|
||||
List[str]: Whisper variants for user input.
|
||||
|
|
@ -606,17 +621,63 @@ class Creator(BaseCreator):
|
|||
|
||||
return copy.deepcopy(self.default_variants)
|
||||
|
||||
def get_default_variant(self):
|
||||
def get_default_variant(self, only_explicit=False):
|
||||
"""Default variant value that will be used to prefill variant input.
|
||||
|
||||
This is for user input and value may not be content of result from
|
||||
`get_default_variants`.
|
||||
|
||||
Can return `None`. In that case first element from
|
||||
`get_default_variants` should be used.
|
||||
Note:
|
||||
This method does not allow to have empty string as
|
||||
default variant.
|
||||
|
||||
Args:
|
||||
only_explicit (Optional[bool]): If True, only explicit default
|
||||
variant from '_default_variant' will be returned.
|
||||
|
||||
Returns:
|
||||
str: Variant value.
|
||||
"""
|
||||
|
||||
return self.default_variant
|
||||
if only_explicit or self._default_variant:
|
||||
return self._default_variant
|
||||
|
||||
for variant in self.get_default_variants():
|
||||
return variant
|
||||
return DEFAULT_VARIANT_VALUE
|
||||
|
||||
def _get_default_variant_wrap(self):
|
||||
"""Default variant value that will be used to prefill variant input.
|
||||
|
||||
Wrapper for 'get_default_variant'.
|
||||
|
||||
Notes:
|
||||
This method is wrapper for 'get_default_variant'
|
||||
for 'default_variant' property, so creator can override
|
||||
the method.
|
||||
|
||||
Returns:
|
||||
str: Variant value.
|
||||
"""
|
||||
|
||||
return self.get_default_variant()
|
||||
|
||||
def _set_default_variant_wrap(self, variant):
|
||||
"""Set default variant value.
|
||||
|
||||
This method is needed for automated settings overrides which are
|
||||
changing attributes based on keys in settings.
|
||||
|
||||
Args:
|
||||
variant (str): New default variant value.
|
||||
"""
|
||||
|
||||
self._default_variant = variant
|
||||
|
||||
default_variant = property(
|
||||
_get_default_variant_wrap,
|
||||
_set_default_variant_wrap
|
||||
)
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
"""Plugin attribute definitions needed for creation.
|
||||
|
|
|
|||
|
|
@ -116,8 +116,8 @@ def get_time_data_from_instance_or_context(instance):
|
|||
instance.context.data.get("fps")),
|
||||
handle_start=(instance.data.get("handleStart") or
|
||||
instance.context.data.get("handleStart")), # noqa: E501
|
||||
handle_end=(instance.data.get("handleStart") or
|
||||
instance.context.data.get("handleStart"))
|
||||
handle_end=(instance.data.get("handleEnd") or
|
||||
instance.context.data.get("handleEnd"))
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import copy
|
|||
import logging
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.lib import Logger
|
||||
from openpype.client import get_project
|
||||
from . import legacy_io
|
||||
from .anatomy import Anatomy
|
||||
|
|
@ -11,13 +12,13 @@ from .plugin_discover import (
|
|||
register_plugin,
|
||||
register_plugin_path,
|
||||
)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_thumbnail_binary(thumbnail_entity, thumbnail_type, dbcon=None):
|
||||
if not thumbnail_entity:
|
||||
return
|
||||
|
||||
log = Logger.get_logger(__name__)
|
||||
resolvers = discover_thumbnail_resolvers()
|
||||
resolvers = sorted(resolvers, key=lambda cls: cls.priority)
|
||||
if dbcon is None:
|
||||
|
|
@ -133,6 +134,16 @@ class BinaryThumbnail(ThumbnailResolver):
|
|||
|
||||
|
||||
class ServerThumbnailResolver(ThumbnailResolver):
|
||||
_cache = None
|
||||
|
||||
@classmethod
|
||||
def _get_cache(cls):
|
||||
if cls._cache is None:
|
||||
from openpype.client.server.thumbnails import AYONThumbnailCache
|
||||
|
||||
cls._cache = AYONThumbnailCache()
|
||||
return cls._cache
|
||||
|
||||
def process(self, thumbnail_entity, thumbnail_type):
|
||||
if not AYON_SERVER_ENABLED:
|
||||
return None
|
||||
|
|
@ -142,20 +153,40 @@ class ServerThumbnailResolver(ThumbnailResolver):
|
|||
if not entity_type or not entity_id:
|
||||
return None
|
||||
|
||||
from openpype.client.server.server_api import get_server_api_connection
|
||||
import ayon_api
|
||||
|
||||
project_name = self.dbcon.active_project()
|
||||
thumbnail_id = thumbnail_entity["_id"]
|
||||
con = get_server_api_connection()
|
||||
filepath = con.get_thumbnail(
|
||||
project_name, entity_type, entity_id, thumbnail_id
|
||||
)
|
||||
content = None
|
||||
|
||||
cache = self._get_cache()
|
||||
filepath = cache.get_thumbnail_filepath(project_name, thumbnail_id)
|
||||
if filepath:
|
||||
with open(filepath, "rb") as stream:
|
||||
content = stream.read()
|
||||
return stream.read()
|
||||
|
||||
return content
|
||||
# This is new way how thumbnails can be received from server
|
||||
# - output is 'ThumbnailContent' object
|
||||
if hasattr(ayon_api, "get_thumbnail_by_id"):
|
||||
result = ayon_api.get_thumbnail_by_id(thumbnail_id)
|
||||
if result.is_valid:
|
||||
filepath = cache.store_thumbnail(
|
||||
project_name,
|
||||
thumbnail_id,
|
||||
result.content,
|
||||
result.content_type
|
||||
)
|
||||
else:
|
||||
# Backwards compatibility for ayon api where 'get_thumbnail_by_id'
|
||||
# is not implemented and output is filepath
|
||||
filepath = ayon_api.get_thumbnail(
|
||||
project_name, entity_type, entity_id, thumbnail_id
|
||||
)
|
||||
|
||||
if not filepath:
|
||||
return None
|
||||
|
||||
with open(filepath, "rb") as stream:
|
||||
return stream.read()
|
||||
|
||||
|
||||
# Thumbnail resolvers
|
||||
|
|
|
|||
37
openpype/pipeline/version_start.py
Normal file
37
openpype/pipeline/version_start.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
from openpype.lib.profiles_filtering import filter_profiles
|
||||
from openpype.settings import get_project_settings
|
||||
|
||||
|
||||
def get_versioning_start(
|
||||
project_name,
|
||||
host_name,
|
||||
task_name=None,
|
||||
task_type=None,
|
||||
family=None,
|
||||
subset=None,
|
||||
project_settings=None,
|
||||
):
|
||||
"""Get anatomy versioning start"""
|
||||
if not project_settings:
|
||||
project_settings = get_project_settings(project_name)
|
||||
|
||||
version_start = 1
|
||||
settings = project_settings["global"]
|
||||
profiles = settings.get("version_start_category", {}).get("profiles", [])
|
||||
|
||||
if not profiles:
|
||||
return version_start
|
||||
|
||||
filtering_criteria = {
|
||||
"host_names": host_name,
|
||||
"families": family,
|
||||
"task_names": task_name,
|
||||
"task_types": task_type,
|
||||
"subsets": subset
|
||||
}
|
||||
profile = filter_profiles(profiles, filtering_criteria)
|
||||
|
||||
if profile is None:
|
||||
return version_start
|
||||
|
||||
return profile["version_start"]
|
||||
|
|
@ -10,7 +10,7 @@ from openpype.lib import (
|
|||
Logger,
|
||||
StringTemplate,
|
||||
)
|
||||
from openpype.pipeline import Anatomy
|
||||
from openpype.pipeline import version_start, Anatomy
|
||||
from openpype.pipeline.template_data import get_template_data
|
||||
|
||||
|
||||
|
|
@ -316,7 +316,13 @@ def get_last_workfile(
|
|||
)
|
||||
if filename is None:
|
||||
data = copy.deepcopy(fill_data)
|
||||
data["version"] = 1
|
||||
data["version"] = version_start.get_versioning_start(
|
||||
data["project"]["name"],
|
||||
data["app"],
|
||||
task_name=data["task"]["name"],
|
||||
task_type=data["task"]["type"],
|
||||
family="workfile"
|
||||
)
|
||||
data.pop("comment", None)
|
||||
if not data.get("ext"):
|
||||
data["ext"] = extensions[0]
|
||||
|
|
|
|||
|
|
@ -1612,7 +1612,7 @@ class PlaceholderLoadMixin(object):
|
|||
|
||||
pass
|
||||
|
||||
def delete_placeholder(self, placeholder, failed):
|
||||
def delete_placeholder(self, placeholder):
|
||||
"""Called when all item population is done."""
|
||||
self.log.debug("Clean up of placeholder is not implemented.")
|
||||
|
||||
|
|
@ -1781,6 +1781,17 @@ class PlaceholderCreateMixin(object):
|
|||
|
||||
self.post_placeholder_process(placeholder, failed)
|
||||
|
||||
if failed:
|
||||
self.log.debug(
|
||||
"Placeholder cleanup skipped due to failed placeholder "
|
||||
"population."
|
||||
)
|
||||
return
|
||||
|
||||
if not placeholder.data.get("keep_placeholder", True):
|
||||
self.delete_placeholder(placeholder)
|
||||
|
||||
|
||||
def create_failed(self, placeholder, creator_data):
|
||||
if hasattr(placeholder, "create_failed"):
|
||||
placeholder.create_failed(creator_data)
|
||||
|
|
@ -1800,9 +1811,12 @@ class PlaceholderCreateMixin(object):
|
|||
representation.
|
||||
failed (bool): Loading of representation failed.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def delete_placeholder(self, placeholder):
|
||||
"""Called when all item population is done."""
|
||||
self.log.debug("Clean up of placeholder is not implemented.")
|
||||
|
||||
def _before_instance_create(self, placeholder):
|
||||
"""Can be overriden. Is called before instance is created."""
|
||||
|
||||
|
|
|
|||
125
openpype/plugins/actions/open_file_explorer.py
Normal file
125
openpype/plugins/actions/open_file_explorer.py
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
from string import Formatter
|
||||
from openpype.client import (
|
||||
get_project,
|
||||
get_asset_by_name,
|
||||
)
|
||||
from openpype.pipeline import (
|
||||
Anatomy,
|
||||
LauncherAction,
|
||||
)
|
||||
from openpype.pipeline.template_data import get_template_data
|
||||
|
||||
|
||||
class OpenTaskPath(LauncherAction):
|
||||
name = "open_task_path"
|
||||
label = "Explore here"
|
||||
icon = "folder-open"
|
||||
order = 500
|
||||
|
||||
def is_compatible(self, session):
|
||||
"""Return whether the action is compatible with the session"""
|
||||
return bool(session.get("AVALON_ASSET"))
|
||||
|
||||
def process(self, session, **kwargs):
|
||||
from qtpy import QtCore, QtWidgets
|
||||
|
||||
project_name = session["AVALON_PROJECT"]
|
||||
asset_name = session["AVALON_ASSET"]
|
||||
task_name = session.get("AVALON_TASK", None)
|
||||
|
||||
path = self._get_workdir(project_name, asset_name, task_name)
|
||||
if not path:
|
||||
return
|
||||
|
||||
app = QtWidgets.QApplication.instance()
|
||||
ctrl_pressed = QtCore.Qt.ControlModifier & app.keyboardModifiers()
|
||||
if ctrl_pressed:
|
||||
# Copy path to clipboard
|
||||
self.copy_path_to_clipboard(path)
|
||||
else:
|
||||
self.open_in_explorer(path)
|
||||
|
||||
def _find_first_filled_path(self, path):
|
||||
if not path:
|
||||
return ""
|
||||
|
||||
fields = set()
|
||||
for item in Formatter().parse(path):
|
||||
_, field_name, format_spec, conversion = item
|
||||
if not field_name:
|
||||
continue
|
||||
conversion = "!{}".format(conversion) if conversion else ""
|
||||
format_spec = ":{}".format(format_spec) if format_spec else ""
|
||||
orig_key = "{{{}{}{}}}".format(
|
||||
field_name, conversion, format_spec)
|
||||
fields.add(orig_key)
|
||||
|
||||
for field in fields:
|
||||
path = path.split(field, 1)[0]
|
||||
return path
|
||||
|
||||
def _get_workdir(self, project_name, asset_name, task_name):
|
||||
project = get_project(project_name)
|
||||
asset = get_asset_by_name(project_name, asset_name)
|
||||
|
||||
data = get_template_data(project, asset, task_name)
|
||||
|
||||
anatomy = Anatomy(project_name)
|
||||
workdir = anatomy.templates_obj["work"]["folder"].format(data)
|
||||
|
||||
# Remove any potential un-formatted parts of the path
|
||||
valid_workdir = self._find_first_filled_path(workdir)
|
||||
|
||||
# Path is not filled at all
|
||||
if not valid_workdir:
|
||||
raise AssertionError("Failed to calculate workdir.")
|
||||
|
||||
# Normalize
|
||||
valid_workdir = os.path.normpath(valid_workdir)
|
||||
if os.path.exists(valid_workdir):
|
||||
return valid_workdir
|
||||
|
||||
# If task was selected, try to find asset path only to asset
|
||||
if not task_name:
|
||||
raise AssertionError("Folder does not exist.")
|
||||
|
||||
data.pop("task", None)
|
||||
workdir = anatomy.templates_obj["work"]["folder"].format(data)
|
||||
valid_workdir = self._find_first_filled_path(workdir)
|
||||
if valid_workdir:
|
||||
# Normalize
|
||||
valid_workdir = os.path.normpath(valid_workdir)
|
||||
if os.path.exists(valid_workdir):
|
||||
return valid_workdir
|
||||
raise AssertionError("Folder does not exist.")
|
||||
|
||||
@staticmethod
|
||||
def open_in_explorer(path):
|
||||
platform_name = platform.system().lower()
|
||||
if platform_name == "windows":
|
||||
args = ["start", path]
|
||||
elif platform_name == "darwin":
|
||||
args = ["open", "-na", path]
|
||||
elif platform_name == "linux":
|
||||
args = ["xdg-open", path]
|
||||
else:
|
||||
raise RuntimeError(f"Unknown platform {platform.system()}")
|
||||
# Make sure path is converted correctly for 'os.system'
|
||||
os.system(subprocess.list2cmdline(args))
|
||||
|
||||
@staticmethod
|
||||
def copy_path_to_clipboard(path):
|
||||
from qtpy import QtWidgets
|
||||
|
||||
path = path.replace("\\", "/")
|
||||
print(f"Copied to clipboard: {path}")
|
||||
app = QtWidgets.QApplication.instance()
|
||||
assert app, "Must have running QApplication instance"
|
||||
|
||||
# Set to Clipboard
|
||||
clipboard = QtWidgets.QApplication.clipboard()
|
||||
clipboard.setText(os.path.normpath(path))
|
||||
|
|
@ -32,6 +32,7 @@ from openpype.client import (
|
|||
get_subsets,
|
||||
get_last_versions
|
||||
)
|
||||
from openpype.pipeline.version_start import get_versioning_start
|
||||
|
||||
|
||||
class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
|
||||
|
|
@ -187,25 +188,13 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
|
|||
project_task_types = project_doc["config"]["tasks"]
|
||||
|
||||
for instance in context:
|
||||
if self.follow_workfile_version:
|
||||
version_number = context.data('version')
|
||||
else:
|
||||
version_number = instance.data.get("version")
|
||||
# If version is not specified for instance or context
|
||||
if version_number is None:
|
||||
# TODO we should be able to change default version by studio
|
||||
# preferences (like start with version number `0`)
|
||||
version_number = 1
|
||||
# use latest version (+1) if already any exist
|
||||
latest_version = instance.data["latestVersion"]
|
||||
if latest_version is not None:
|
||||
version_number += int(latest_version)
|
||||
|
||||
anatomy_updates = {
|
||||
"asset": instance.data["asset"],
|
||||
"folder": {
|
||||
"name": instance.data["asset"],
|
||||
},
|
||||
"family": instance.data["family"],
|
||||
"subset": instance.data["subset"],
|
||||
"version": version_number
|
||||
}
|
||||
|
||||
# Hierarchy
|
||||
|
|
@ -225,6 +214,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
|
|||
anatomy_updates["parent"] = parent_name
|
||||
|
||||
# Task
|
||||
task_type = None
|
||||
task_name = instance.data.get("task")
|
||||
if task_name:
|
||||
asset_tasks = asset_doc["data"]["tasks"]
|
||||
|
|
@ -240,6 +230,30 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
|
|||
"short": task_code
|
||||
}
|
||||
|
||||
# Define version
|
||||
if self.follow_workfile_version:
|
||||
version_number = context.data('version')
|
||||
else:
|
||||
version_number = instance.data.get("version")
|
||||
|
||||
# use latest version (+1) if already any exist
|
||||
if version_number is None:
|
||||
latest_version = instance.data["latestVersion"]
|
||||
if latest_version is not None:
|
||||
version_number = int(latest_version) + 1
|
||||
|
||||
# If version is not specified for instance or context
|
||||
if version_number is None:
|
||||
version_number = get_versioning_start(
|
||||
context.data["projectName"],
|
||||
instance.context.data["hostName"],
|
||||
task_name=task_name,
|
||||
task_type=task_type,
|
||||
family=instance.data["family"],
|
||||
subset=instance.data["subset"]
|
||||
)
|
||||
anatomy_updates["version"] = version_number
|
||||
|
||||
# Additional data
|
||||
resolution_width = instance.data.get("resolutionWidth")
|
||||
if resolution_width:
|
||||
|
|
|
|||
|
|
@ -142,6 +142,12 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin):
|
|||
))
|
||||
return
|
||||
|
||||
if AYON_SERVER_ENABLED and src_version_entity["name"] == 0:
|
||||
self.log.debug(
|
||||
"Version 0 cannot have hero version. Skipping."
|
||||
)
|
||||
return
|
||||
|
||||
all_copied_files = []
|
||||
transfers = instance.data.get("transfers", list())
|
||||
for _src, dst in transfers:
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ from openpype.pipeline import (
|
|||
)
|
||||
|
||||
from openpype.pipeline.context_tools import get_workdir_from_session
|
||||
from openpype.pipeline.version_start import get_versioning_start
|
||||
|
||||
log = logging.getLogger("Update Slap Comp")
|
||||
|
||||
|
|
@ -26,9 +27,6 @@ log = logging.getLogger("Update Slap Comp")
|
|||
def _format_version_folder(folder):
|
||||
"""Format a version folder based on the filepath
|
||||
|
||||
Assumption here is made that, if the path does not exists the folder
|
||||
will be "v001"
|
||||
|
||||
Args:
|
||||
folder: file path to a folder
|
||||
|
||||
|
|
@ -36,9 +34,13 @@ def _format_version_folder(folder):
|
|||
str: new version folder name
|
||||
"""
|
||||
|
||||
new_version = 1
|
||||
new_version = get_versioning_start(
|
||||
get_current_project_name(),
|
||||
"fusion",
|
||||
family="workfile"
|
||||
)
|
||||
if os.path.isdir(folder):
|
||||
re_version = re.compile("v\d+$")
|
||||
re_version = re.compile(r"v\d+$")
|
||||
versions = [i for i in os.listdir(folder) if os.path.isdir(i)
|
||||
and re_version.match(i)]
|
||||
if versions:
|
||||
|
|
|
|||
|
|
@ -301,6 +301,10 @@ def convert_system_settings(ayon_settings, default_settings, addon_versions):
|
|||
if "core" in ayon_settings:
|
||||
_convert_general(ayon_settings, output, default_settings)
|
||||
|
||||
for key, value in ayon_settings.items():
|
||||
if key not in output:
|
||||
output[key] = value
|
||||
|
||||
for key, value in default_settings.items():
|
||||
if key not in output:
|
||||
output[key] = value
|
||||
|
|
@ -602,6 +606,13 @@ def _convert_maya_project_settings(ayon_settings, output):
|
|||
.replace("{product[name]}", "{subset}")
|
||||
)
|
||||
|
||||
if ayon_maya_load.get("import_loader"):
|
||||
import_loader = ayon_maya_load["import_loader"]
|
||||
import_loader["namespace"] = (
|
||||
import_loader["namespace"]
|
||||
.replace("{product[name]}", "{subset}")
|
||||
)
|
||||
|
||||
output["maya"] = ayon_maya
|
||||
|
||||
|
||||
|
|
@ -1265,6 +1276,10 @@ def convert_project_settings(ayon_settings, default_settings):
|
|||
|
||||
_convert_global_project_settings(ayon_settings, output, default_settings)
|
||||
|
||||
for key, value in ayon_settings.items():
|
||||
if key not in output:
|
||||
output[key] = value
|
||||
|
||||
for key, value in default_settings.items():
|
||||
if key not in output:
|
||||
output[key] = value
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
},
|
||||
"create": {
|
||||
"RenderCreator": {
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
],
|
||||
"mark_for_review": true
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
"version_start_category": {
|
||||
"profiles": []
|
||||
},
|
||||
"imageio": {
|
||||
"activate_global_color_management": false,
|
||||
"ocio_config": {
|
||||
|
|
|
|||
|
|
@ -14,48 +14,70 @@
|
|||
"create": {
|
||||
"CreateArnoldAss": {
|
||||
"enabled": true,
|
||||
"default_variants": [],
|
||||
"default_variants": [
|
||||
"Main"
|
||||
],
|
||||
"ext": ".ass"
|
||||
},
|
||||
"CreateAlembicCamera": {
|
||||
"enabled": true,
|
||||
"defaults": []
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateCompositeSequence": {
|
||||
"enabled": true,
|
||||
"defaults": []
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreatePointCache": {
|
||||
"enabled": true,
|
||||
"defaults": []
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateRedshiftROP": {
|
||||
"enabled": true,
|
||||
"defaults": []
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateRemotePublish": {
|
||||
"enabled": true,
|
||||
"defaults": []
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateVDBCache": {
|
||||
"enabled": true,
|
||||
"defaults": []
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateUSD": {
|
||||
"enabled": false,
|
||||
"defaults": []
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateUSDModel": {
|
||||
"enabled": false,
|
||||
"defaults": []
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"USDCreateShadingWorkspace": {
|
||||
"enabled": false,
|
||||
"defaults": []
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateUSDRender": {
|
||||
"enabled": false,
|
||||
"defaults": []
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
}
|
||||
},
|
||||
"publish": {
|
||||
|
|
|
|||
|
|
@ -527,7 +527,7 @@
|
|||
},
|
||||
"CreateRender": {
|
||||
"enabled": true,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
|
|
@ -547,7 +547,9 @@
|
|||
},
|
||||
"CreateUnrealSkeletalMesh": {
|
||||
"enabled": true,
|
||||
"default_variants": [],
|
||||
"default_variants": [
|
||||
"Main"
|
||||
],
|
||||
"joint_hints": "jnt_org"
|
||||
},
|
||||
"CreateMultiverseLook": {
|
||||
|
|
@ -627,55 +629,55 @@
|
|||
},
|
||||
"CreateMultiverseUsd": {
|
||||
"enabled": true,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateMultiverseUsdComp": {
|
||||
"enabled": true,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateMultiverseUsdOver": {
|
||||
"enabled": true,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateAssembly": {
|
||||
"enabled": true,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateCamera": {
|
||||
"enabled": true,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateLayout": {
|
||||
"enabled": true,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateMayaScene": {
|
||||
"enabled": true,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateRenderSetup": {
|
||||
"enabled": true,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateRig": {
|
||||
"enabled": true,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main",
|
||||
"Sim",
|
||||
"Cloth"
|
||||
|
|
@ -683,20 +685,20 @@
|
|||
},
|
||||
"CreateSetDress": {
|
||||
"enabled": true,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main",
|
||||
"Anim"
|
||||
]
|
||||
},
|
||||
"CreateVRayScene": {
|
||||
"enabled": true,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateYetiRig": {
|
||||
"enabled": true,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
}
|
||||
|
|
@ -1463,6 +1465,10 @@
|
|||
"namespace": "{asset_name}_{subset}_##_",
|
||||
"group_name": "_GRP",
|
||||
"display_handle": true
|
||||
},
|
||||
"import_loader": {
|
||||
"namespace": "{asset_name}_{subset}_##_",
|
||||
"group_name": "_GRP"
|
||||
}
|
||||
},
|
||||
"workfile_build": {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
"children": [
|
||||
{
|
||||
"type": "list",
|
||||
"key": "defaults",
|
||||
"key": "default_variants",
|
||||
"label": "Default Variants",
|
||||
"object_type": "text",
|
||||
"docstring": "Fill default variant(s) (like 'Main' or 'Default') used in subset name creation."
|
||||
|
|
|
|||
|
|
@ -5,6 +5,61 @@
|
|||
"label": "Global",
|
||||
"is_file": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "dict",
|
||||
"key": "version_start_category",
|
||||
"label": "Version Start",
|
||||
"collapsible": true,
|
||||
"collapsible_key": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "list",
|
||||
"collapsible": true,
|
||||
"key": "profiles",
|
||||
"label": "Profiles",
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"key": "host_names",
|
||||
"label": "Host names",
|
||||
"type": "hosts-enum",
|
||||
"multiselection": true
|
||||
},
|
||||
{
|
||||
"key": "task_types",
|
||||
"label": "Task types",
|
||||
"type": "task-types-enum"
|
||||
},
|
||||
{
|
||||
"key": "task_names",
|
||||
"label": "Task names",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"key": "families",
|
||||
"label": "Families",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"key": "subsets",
|
||||
"label": "Subset names",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"key": "version_start",
|
||||
"label": "Version Start",
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "imageio",
|
||||
"type": "dict",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
{
|
||||
"type": "list",
|
||||
"key": "default_variants",
|
||||
"label": "Default Subsets",
|
||||
"label": "Default Variants",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
|
|
@ -39,51 +39,51 @@
|
|||
]
|
||||
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_create_plugin",
|
||||
"template_data": [
|
||||
{
|
||||
"key": "CreateAlembicCamera",
|
||||
"label": "Create Alembic Camera"
|
||||
},
|
||||
{
|
||||
"key": "CreateCompositeSequence",
|
||||
"label": "Create Composite (Image Sequence)"
|
||||
},
|
||||
{
|
||||
"key": "CreatePointCache",
|
||||
"label": "Create Point Cache"
|
||||
},
|
||||
{
|
||||
"key": "CreateRedshiftROP",
|
||||
"label": "Create Redshift ROP"
|
||||
},
|
||||
{
|
||||
"key": "CreateRemotePublish",
|
||||
"label": "Create Remote Publish"
|
||||
},
|
||||
{
|
||||
"key": "CreateVDBCache",
|
||||
"label": "Create VDB Cache"
|
||||
},
|
||||
{
|
||||
"key": "CreateUSD",
|
||||
"label": "Create USD"
|
||||
},
|
||||
{
|
||||
"key": "CreateUSDModel",
|
||||
"label": "Create USD Model"
|
||||
},
|
||||
{
|
||||
"key": "USDCreateShadingWorkspace",
|
||||
"label": "Create USD Shading Workspace"
|
||||
},
|
||||
{
|
||||
"key": "CreateUSDRender",
|
||||
"label": "Create USD Render"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_create_plugin",
|
||||
"template_data": [
|
||||
{
|
||||
"key": "CreateAlembicCamera",
|
||||
"label": "Create Alembic Camera"
|
||||
},
|
||||
{
|
||||
"key": "CreateCompositeSequence",
|
||||
"label": "Create Composite (Image Sequence)"
|
||||
},
|
||||
{
|
||||
"key": "CreatePointCache",
|
||||
"label": "Create Point Cache"
|
||||
},
|
||||
{
|
||||
"key": "CreateRedshiftROP",
|
||||
"label": "Create Redshift ROP"
|
||||
},
|
||||
{
|
||||
"key": "CreateRemotePublish",
|
||||
"label": "Create Remote Publish"
|
||||
},
|
||||
{
|
||||
"key": "CreateVDBCache",
|
||||
"label": "Create VDB Cache"
|
||||
},
|
||||
{
|
||||
"key": "CreateUSD",
|
||||
"label": "Create USD"
|
||||
},
|
||||
{
|
||||
"key": "CreateUSDModel",
|
||||
"label": "Create USD Model"
|
||||
},
|
||||
{
|
||||
"key": "USDCreateShadingWorkspace",
|
||||
"label": "Create USD Shading Workspace"
|
||||
},
|
||||
{
|
||||
"key": "CreateUSDRender",
|
||||
"label": "Create USD Render"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,14 +29,20 @@
|
|||
{
|
||||
"type": "list",
|
||||
"key": "default_variants",
|
||||
"label": "Default Subsets",
|
||||
"label": "Default Variants",
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_maya_create_render"
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_create_plugin",
|
||||
"template_data": [
|
||||
{
|
||||
"key": "CreateRender",
|
||||
"label": "Create Render"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
|
|
@ -53,7 +59,7 @@
|
|||
{
|
||||
"type": "list",
|
||||
"key": "default_variants",
|
||||
"label": "Default Subsets",
|
||||
"label": "Default Variants",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
|
|
@ -85,7 +91,7 @@
|
|||
{
|
||||
"type": "list",
|
||||
"key": "default_variants",
|
||||
"label": "Default Subsets",
|
||||
"label": "Default Variants",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
|
|
@ -148,7 +154,7 @@
|
|||
{
|
||||
"type": "list",
|
||||
"key": "default_variants",
|
||||
"label": "Default Subsets",
|
||||
"label": "Default Variants",
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
|
|
@ -178,7 +184,7 @@
|
|||
{
|
||||
"type": "list",
|
||||
"key": "default_variants",
|
||||
"label": "Default Subsets",
|
||||
"label": "Default Variants",
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
|
|
@ -213,7 +219,7 @@
|
|||
{
|
||||
"type": "list",
|
||||
"key": "default_variants",
|
||||
"label": "Default Subsets",
|
||||
"label": "Default Variants",
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
|
|
@ -243,7 +249,7 @@
|
|||
{
|
||||
"type": "list",
|
||||
"key": "default_variants",
|
||||
"label": "Default Subsets",
|
||||
"label": "Default Variants",
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
|
|
@ -263,7 +269,7 @@
|
|||
{
|
||||
"type": "list",
|
||||
"key": "default_variants",
|
||||
"label": "Default Subsets",
|
||||
"label": "Default Variants",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
|
|
@ -288,7 +294,7 @@
|
|||
{
|
||||
"type": "list",
|
||||
"key": "default_variants",
|
||||
"label": "Default Subsets",
|
||||
"label": "Default Variants",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
|
|
@ -390,7 +396,7 @@
|
|||
{
|
||||
"type": "list",
|
||||
"key": "default_variants",
|
||||
"label": "Default Subsets",
|
||||
"label": "Default Variants",
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "CreateRender",
|
||||
"label": "Create Render",
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "defaults",
|
||||
"label": "Default Subsets",
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -121,6 +121,28 @@
|
|||
"label": "Display Handle On Load References"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "import_loader",
|
||||
"label": "Import 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@
|
|||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "defaults",
|
||||
"label": "Default Subsets",
|
||||
"key": "default_variants",
|
||||
"label": "Default Variants",
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from openpype import AYON_SERVER_ENABLED
|
|||
from openpype.pipeline.create import (
|
||||
SUBSET_NAME_ALLOWED_SYMBOLS,
|
||||
PRE_CREATE_THUMBNAIL_KEY,
|
||||
DEFAULT_VARIANT_VALUE,
|
||||
TaskNotSetError,
|
||||
)
|
||||
|
||||
|
|
@ -626,7 +627,7 @@ class CreateWidget(QtWidgets.QWidget):
|
|||
|
||||
default_variants = creator_item.default_variants
|
||||
if not default_variants:
|
||||
default_variants = ["Main"]
|
||||
default_variants = [DEFAULT_VARIANT_VALUE]
|
||||
|
||||
default_variant = creator_item.default_variant
|
||||
if not default_variant:
|
||||
|
|
@ -642,7 +643,7 @@ class CreateWidget(QtWidgets.QWidget):
|
|||
elif variant:
|
||||
self.variant_hints_menu.addAction(variant)
|
||||
|
||||
variant_text = default_variant or "Main"
|
||||
variant_text = default_variant or DEFAULT_VARIANT_VALUE
|
||||
# Make sure subset name is updated to new plugin
|
||||
if variant_text == self.variant_input.text():
|
||||
self._on_variant_change()
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ from openpype.lib import (
|
|||
from openpype.lib.file_transaction import FileTransaction
|
||||
from openpype.settings import get_project_settings
|
||||
from openpype.pipeline import Anatomy
|
||||
from openpype.pipeline.version_start import get_versioning_start
|
||||
from openpype.pipeline.template_data import get_template_data
|
||||
from openpype.pipeline.publish import get_publish_template_name
|
||||
from openpype.pipeline.create import get_subset_name
|
||||
|
|
@ -940,9 +941,17 @@ class ProjectPushItemProcess:
|
|||
last_version_doc = get_last_version_by_subset_id(
|
||||
project_name, subset_id
|
||||
)
|
||||
version = 1
|
||||
if last_version_doc:
|
||||
version += int(last_version_doc["name"])
|
||||
version = int(last_version_doc["name"]) + 1
|
||||
else:
|
||||
version = get_versioning_start(
|
||||
project_name,
|
||||
self.host_name,
|
||||
task_name=self.task_info["name"],
|
||||
task_type=self.task_info["type"],
|
||||
family=families[0],
|
||||
subset=subset_doc["name"]
|
||||
)
|
||||
|
||||
existing_version_doc = get_version_by_name(
|
||||
project_name, version, subset_id
|
||||
|
|
@ -966,14 +975,6 @@ class ProjectPushItemProcess:
|
|||
|
||||
return
|
||||
|
||||
if version is None:
|
||||
last_version_doc = get_last_version_by_subset_id(
|
||||
project_name, subset_id
|
||||
)
|
||||
version = 1
|
||||
if last_version_doc:
|
||||
version += int(last_version_doc["name"])
|
||||
|
||||
version_doc = new_version_doc(
|
||||
version, subset_id, version_data
|
||||
)
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ class InventoryModel(TreeModel):
|
|||
self.remote_provider = remote_provider
|
||||
self._site_icons = {
|
||||
provider: QtGui.QIcon(icon_path)
|
||||
for provider, icon_path in self.get_site_icons().items()
|
||||
for provider, icon_path in sync_server.get_site_icons().items()
|
||||
}
|
||||
if "active_site" not in self.Columns:
|
||||
self.Columns.append("active_site")
|
||||
|
|
|
|||
|
|
@ -286,7 +286,7 @@ class SitesWidget(QtWidgets.QWidget):
|
|||
continue
|
||||
|
||||
site_inputs = []
|
||||
site_config = site_configs[site_name]
|
||||
site_config = site_configs.get(site_name, {})
|
||||
for root_name, path_entity in site_config.get("root", {}).items():
|
||||
if not path_entity:
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from openpype.client import (
|
|||
)
|
||||
from openpype.settings import get_project_settings
|
||||
from openpype.pipeline import LegacyCreator
|
||||
from openpype.pipeline.version_start import get_versioning_start
|
||||
from openpype.pipeline.create import (
|
||||
SUBSET_NAME_ALLOWED_SYMBOLS,
|
||||
TaskNotSetError,
|
||||
|
|
@ -299,7 +300,15 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
project_name = self.dbcon.active_project()
|
||||
asset_name = self.asset_name
|
||||
subset_name = self.input_result.text()
|
||||
version = 1
|
||||
plugin = self.list_families.currentItem().data(PluginRole)
|
||||
family = plugin.family.rsplit(".", 1)[-1]
|
||||
version = get_versioning_start(
|
||||
project_name,
|
||||
"standalonepublisher",
|
||||
task_name=self.dbcon.Session["AVALON_TASK"],
|
||||
family=family,
|
||||
subset=subset_name
|
||||
)
|
||||
|
||||
asset_doc = None
|
||||
subset_doc = None
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from openpype.pipeline import (
|
|||
from openpype.pipeline.workfile import get_last_workfile_with_version
|
||||
from openpype.pipeline.template_data import get_template_data_with_names
|
||||
from openpype.tools.utils import PlaceholderLineEdit
|
||||
from openpype.pipeline import version_start, get_current_host_name
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -218,7 +219,15 @@ class SaveAsDialog(QtWidgets.QDialog):
|
|||
|
||||
# Version number input
|
||||
version_input = QtWidgets.QSpinBox(version_widget)
|
||||
version_input.setMinimum(1)
|
||||
version_input.setMinimum(
|
||||
version_start.get_versioning_start(
|
||||
self.data["project"]["name"],
|
||||
get_current_host_name(),
|
||||
task_name=self.data["task"]["name"],
|
||||
task_type=self.data["task"]["type"],
|
||||
family="workfile"
|
||||
)
|
||||
)
|
||||
version_input.setMaximum(9999)
|
||||
|
||||
# Last version checkbox
|
||||
|
|
@ -420,7 +429,13 @@ class SaveAsDialog(QtWidgets.QDialog):
|
|||
)[1]
|
||||
|
||||
if version is None:
|
||||
version = 1
|
||||
version = version_start.get_versioning_start(
|
||||
data["project"]["name"],
|
||||
get_current_host_name(),
|
||||
task_name=self.data["task"]["name"],
|
||||
task_type=self.data["task"]["type"],
|
||||
family="workfile"
|
||||
)
|
||||
else:
|
||||
version += 1
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring Pype version."""
|
||||
__version__ = "3.16.3-nightly.5"
|
||||
__version__ = "3.16.4-nightly.1"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "OpenPype"
|
||||
version = "3.16.2" # OpenPype
|
||||
version = "3.16.3" # OpenPype
|
||||
description = "Open VFX and Animation pipeline with support."
|
||||
authors = ["OpenPype Team <info@openpype.io>"]
|
||||
license = "MIT License"
|
||||
|
|
|
|||
|
|
@ -6,12 +6,18 @@ from ayon_server.settings import BaseSettingsModel
|
|||
# Creator Plugins
|
||||
class CreatorModel(BaseSettingsModel):
|
||||
enabled: bool = Field(title="Enabled")
|
||||
defaults: list[str] = Field(title="Default Products")
|
||||
default_variants: list[str] = Field(
|
||||
title="Default Products",
|
||||
default_factory=list,
|
||||
)
|
||||
|
||||
|
||||
class CreateArnoldAssModel(BaseSettingsModel):
|
||||
enabled: bool = Field(title="Enabled")
|
||||
defaults: list[str] = Field(title="Default Products")
|
||||
default_variants: list[str] = Field(
|
||||
title="Default Products",
|
||||
default_factory=list,
|
||||
)
|
||||
ext: str = Field(Title="Extension")
|
||||
|
||||
|
||||
|
|
@ -54,49 +60,49 @@ class CreatePluginsModel(BaseSettingsModel):
|
|||
DEFAULT_HOUDINI_CREATE_SETTINGS = {
|
||||
"CreateArnoldAss": {
|
||||
"enabled": True,
|
||||
"default_variants": [],
|
||||
"default_variants": ["Main"],
|
||||
"ext": ".ass"
|
||||
},
|
||||
"CreateAlembicCamera": {
|
||||
"enabled": True,
|
||||
"defaults": []
|
||||
"default_variants": ["Main"]
|
||||
},
|
||||
"CreateCompositeSequence": {
|
||||
"enabled": True,
|
||||
"defaults": []
|
||||
"default_variants": ["Main"]
|
||||
},
|
||||
"CreatePointCache": {
|
||||
"enabled": True,
|
||||
"defaults": []
|
||||
"default_variants": ["Main"]
|
||||
},
|
||||
"CreateRedshiftROP": {
|
||||
"enabled": True,
|
||||
"defaults": []
|
||||
"default_variants": ["Main"]
|
||||
},
|
||||
"CreateRemotePublish": {
|
||||
"enabled": True,
|
||||
"defaults": []
|
||||
"default_variants": ["Main"]
|
||||
},
|
||||
"CreateVDBCache": {
|
||||
"enabled": True,
|
||||
"defaults": []
|
||||
"default_variants": ["Main"]
|
||||
},
|
||||
"CreateUSD": {
|
||||
"enabled": False,
|
||||
"defaults": []
|
||||
"default_variants": ["Main"]
|
||||
},
|
||||
"CreateUSDModel": {
|
||||
"enabled": False,
|
||||
"defaults": []
|
||||
"default_variants": ["Main"]
|
||||
},
|
||||
"USDCreateShadingWorkspace": {
|
||||
"enabled": False,
|
||||
"defaults": []
|
||||
"default_variants": ["Main"]
|
||||
},
|
||||
"CreateUSDRender": {
|
||||
"enabled": False,
|
||||
"defaults": []
|
||||
}
|
||||
"default_variants": ["Main"]
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@ class CreateLookModel(BaseSettingsModel):
|
|||
enabled: bool = Field(title="Enabled")
|
||||
make_tx: bool = Field(title="Make tx files")
|
||||
rs_tex: bool = Field(title="Make Redshift texture files")
|
||||
defaults: list[str] = Field(
|
||||
default_factory=["Main"], title="Default Products"
|
||||
default_variants: list[str] = Field(
|
||||
default_factory=list, title="Default Products"
|
||||
)
|
||||
|
||||
|
||||
class BasicCreatorModel(BaseSettingsModel):
|
||||
enabled: bool = Field(title="Enabled")
|
||||
defaults: list[str] = Field(
|
||||
default_variants: list[str] = Field(
|
||||
default_factory=list,
|
||||
title="Default Products"
|
||||
)
|
||||
|
|
@ -22,20 +22,21 @@ class BasicCreatorModel(BaseSettingsModel):
|
|||
|
||||
class CreateUnrealStaticMeshModel(BaseSettingsModel):
|
||||
enabled: bool = Field(title="Enabled")
|
||||
defaults: list[str] = Field(
|
||||
default_factory=["", "_Main"],
|
||||
default_variants: list[str] = Field(
|
||||
default_factory=list,
|
||||
title="Default Products"
|
||||
)
|
||||
static_mesh_prefixes: str = Field("S", title="Static Mesh Prefix")
|
||||
collision_prefixes: list[str] = Field(
|
||||
default_factory=["UBX", "UCP", "USP", "UCX"],
|
||||
default_factory=list,
|
||||
title="Collision Prefixes"
|
||||
)
|
||||
|
||||
|
||||
class CreateUnrealSkeletalMeshModel(BaseSettingsModel):
|
||||
enabled: bool = Field(title="Enabled")
|
||||
defaults: list[str] = Field(default_factory=[], title="Default Products")
|
||||
default_variants: list[str] = Field(
|
||||
default_factory=list, title="Default Products")
|
||||
joint_hints: str = Field("jnt_org", title="Joint root hint")
|
||||
|
||||
|
||||
|
|
@ -48,7 +49,7 @@ class BasicExportMeshModel(BaseSettingsModel):
|
|||
enabled: bool = Field(title="Enabled")
|
||||
write_color_sets: bool = Field(title="Write Color Sets")
|
||||
write_face_sets: bool = Field(title="Write Face Sets")
|
||||
defaults: list[str] = Field(
|
||||
default_variants: list[str] = Field(
|
||||
default_factory=list,
|
||||
title="Default Products"
|
||||
)
|
||||
|
|
@ -61,7 +62,7 @@ class CreateAnimationModel(BaseSettingsModel):
|
|||
title="Include Parent Hierarchy")
|
||||
include_user_defined_attributes: bool = Field(
|
||||
title="Include User Defined Attributes")
|
||||
defaults: list[str] = Field(
|
||||
default_variants: list[str] = Field(
|
||||
default_factory=list,
|
||||
title="Default Products"
|
||||
)
|
||||
|
|
@ -74,8 +75,8 @@ class CreatePointCacheModel(BaseSettingsModel):
|
|||
include_user_defined_attributes: bool = Field(
|
||||
title="Include User Defined Attributes"
|
||||
)
|
||||
defaults: list[str] = Field(
|
||||
default_factory=["Main"],
|
||||
default_variants: list[str] = Field(
|
||||
default_factory=list,
|
||||
title="Default Products"
|
||||
)
|
||||
|
||||
|
|
@ -84,8 +85,8 @@ class CreateProxyAlembicModel(BaseSettingsModel):
|
|||
enabled: bool = Field(title="Enabled")
|
||||
write_color_sets: bool = Field(title="Write Color Sets")
|
||||
write_face_sets: bool = Field(title="Write Face Sets")
|
||||
defaults: list[str] = Field(
|
||||
default_factory=["Main"],
|
||||
default_variants: list[str] = Field(
|
||||
default_factory=list,
|
||||
title="Default Products"
|
||||
)
|
||||
|
||||
|
|
@ -115,7 +116,8 @@ class CreateVrayProxyModel(BaseSettingsModel):
|
|||
enabled: bool = Field(True)
|
||||
vrmesh: bool = Field(title="VrMesh")
|
||||
alembic: bool = Field(title="Alembic")
|
||||
defaults: list[str] = Field(default_factory=list, title="Default Products")
|
||||
default_variants: list[str] = Field(
|
||||
default_factory=list, title="Default Products")
|
||||
|
||||
|
||||
class CreatorsModel(BaseSettingsModel):
|
||||
|
|
@ -230,7 +232,7 @@ DEFAULT_CREATORS_SETTINGS = {
|
|||
},
|
||||
"CreateRender": {
|
||||
"enabled": True,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
|
|
@ -295,19 +297,19 @@ DEFAULT_CREATORS_SETTINGS = {
|
|||
},
|
||||
"CreateMultiverseUsd": {
|
||||
"enabled": True,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateMultiverseUsdComp": {
|
||||
"enabled": True,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateMultiverseUsdOver": {
|
||||
"enabled": True,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
|
|
@ -333,31 +335,31 @@ DEFAULT_CREATORS_SETTINGS = {
|
|||
},
|
||||
"CreateAssembly": {
|
||||
"enabled": True,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateCamera": {
|
||||
"enabled": True,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateLayout": {
|
||||
"enabled": True,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateMayaScene": {
|
||||
"enabled": True,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateRenderSetup": {
|
||||
"enabled": True,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
|
|
@ -370,7 +372,7 @@ DEFAULT_CREATORS_SETTINGS = {
|
|||
},
|
||||
"CreateRig": {
|
||||
"enabled": True,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main",
|
||||
"Sim",
|
||||
"Cloth"
|
||||
|
|
@ -378,7 +380,7 @@ DEFAULT_CREATORS_SETTINGS = {
|
|||
},
|
||||
"CreateSetDress": {
|
||||
"enabled": True,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main",
|
||||
"Anim"
|
||||
]
|
||||
|
|
@ -393,13 +395,13 @@ DEFAULT_CREATORS_SETTINGS = {
|
|||
},
|
||||
"CreateVRayScene": {
|
||||
"enabled": True,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
},
|
||||
"CreateYetiRig": {
|
||||
"enabled": True,
|
||||
"defaults": [
|
||||
"default_variants": [
|
||||
"Main"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,11 @@ class ReferenceLoaderModel(BaseSettingsModel):
|
|||
display_handle: bool = Field(title="Display Handle On Load References")
|
||||
|
||||
|
||||
class ImportLoaderModel(BaseSettingsModel):
|
||||
namespace: str = Field(title="Namespace")
|
||||
group_name: str = Field(title="Group name")
|
||||
|
||||
|
||||
class LoadersModel(BaseSettingsModel):
|
||||
colors: ColorsSetting = Field(
|
||||
default_factory=ColorsSetting,
|
||||
|
|
@ -55,6 +60,10 @@ class LoadersModel(BaseSettingsModel):
|
|||
title="Reference Loader"
|
||||
)
|
||||
|
||||
import_loader: ImportLoaderModel = Field(
|
||||
default_factory=ImportLoaderModel,
|
||||
title="Import Loader"
|
||||
)
|
||||
|
||||
DEFAULT_LOADERS_SETTING = {
|
||||
"colors": {
|
||||
|
|
@ -111,5 +120,10 @@ DEFAULT_LOADERS_SETTING = {
|
|||
"namespace": "{folder[name]}_{product[name]}_##_",
|
||||
"group_name": "_GRP",
|
||||
"display_handle": True
|
||||
},
|
||||
"import_loader": {
|
||||
"namespace": "{folder[name]}_{product[name]}_##_",
|
||||
"group_name": "_GRP",
|
||||
"display_handle": True
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring addon version."""
|
||||
__version__ = "0.1.2"
|
||||
__version__ = "0.1.3"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue