diff --git a/.github_changelog_generator b/.github_changelog_generator index da6c35cebf..ac934ff84f 100644 --- a/.github_changelog_generator +++ b/.github_changelog_generator @@ -2,7 +2,8 @@ pr-wo-labels=False exclude-labels=duplicate,question,invalid,wontfix,weekly-digest author=False unreleased=True -since-tag=2.11.0 +since-tag=2.13.6 release-branch=master enhancement-label=**Enhancements:** issues=False +pulls=False diff --git a/.gitignore b/.gitignore index 4fd2bc117b..b1f6306a99 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,8 @@ __pycache__/ Icon # Thumbnails ._* +# rope project dir +.ropeproject # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd diff --git a/CHANGELOG.md b/CHANGELOG.md index b8b96fb4c3..70e23e0ff8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,48 @@ # Changelog +## [2.14.0](https://github.com/pypeclub/pype/tree/2.14.0) (2020-11-24) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.13.7...2.14.0) + +**Enhancements:** + +- Shot asset build trigger status [\#736](https://github.com/pypeclub/pype/pull/736) +- Maya: add camera rig publishing option [\#721](https://github.com/pypeclub/pype/pull/721) +- Sort instances by label in pyblish gui [\#719](https://github.com/pypeclub/pype/pull/719) +- Synchronize ftrack hierarchical and shot attributes [\#716](https://github.com/pypeclub/pype/pull/716) +- 686 standalonepublisher editorial from image sequences [\#699](https://github.com/pypeclub/pype/pull/699) +- TV Paint: initial implementation of creators and local rendering [\#693](https://github.com/pypeclub/pype/pull/693) +- Render publish plugins abstraction [\#687](https://github.com/pypeclub/pype/pull/687) +- Ask user to select non-default camera from scene or create a new. [\#678](https://github.com/pypeclub/pype/pull/678) +- TVPaint: image loader with options [\#675](https://github.com/pypeclub/pype/pull/675) +- Maya: Camera name can be added to burnins. [\#674](https://github.com/pypeclub/pype/pull/674) +- After Effects: base integration with loaders [\#667](https://github.com/pypeclub/pype/pull/667) +- Harmony: Javascript refactoring and overall stability improvements [\#666](https://github.com/pypeclub/pype/pull/666) + +**Fixed bugs:** + +- TVPaint extract review fix [\#740](https://github.com/pypeclub/pype/pull/740) +- After Effects: Review were not being sent to ftrack [\#738](https://github.com/pypeclub/pype/pull/738) +- Asset fetch second fix [\#726](https://github.com/pypeclub/pype/pull/726) +- Maya: vray proxy was not loading [\#722](https://github.com/pypeclub/pype/pull/722) +- Maya: Vray expected file fixes [\#682](https://github.com/pypeclub/pype/pull/682) + +**Deprecated:** + +- Removed artist view from pyblish gui [\#717](https://github.com/pypeclub/pype/pull/717) +- Maya: disable legacy override check for cameras [\#715](https://github.com/pypeclub/pype/pull/715) + + +## [2.13.7](https://github.com/pypeclub/pype/tree/2.13.7) (2020-11-19) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.13.6...2.13.7) + +**Merged pull requests:** + +- fix\(SP\): getting fps from context instead of nonexistent entity [\#729](https://github.com/pypeclub/pype/pull/729) + +# Changelog + ## [2.13.6](https://github.com/pypeclub/pype/tree/2.13.6) (2020-11-15) [Full Changelog](https://github.com/pypeclub/pype/compare/2.13.5...2.13.6) @@ -789,4 +832,7 @@ A large cleanup release. Most of the change are under the hood. - _(avalon)_ subsets in maya 2019 weren't behaving correctly in the outliner +\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* + + \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* diff --git a/HISTORY.md b/HISTORY.md index d60bd7b0c7..b8b96fb4c3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,360 @@ +# Changelog + +## [2.13.6](https://github.com/pypeclub/pype/tree/2.13.6) (2020-11-15) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.13.5...2.13.6) + +**Fixed bugs:** + +- Maya workfile version wasn't syncing with renders properly [\#711](https://github.com/pypeclub/pype/pull/711) +- Maya: Fix for publishing multiple cameras with review from the same scene [\#710](https://github.com/pypeclub/pype/pull/710) + +## [2.13.5](https://github.com/pypeclub/pype/tree/2.13.5) (2020-11-12) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.13.4...2.13.5) + +**Enhancements:** + +- 3.0 lib refactor [\#664](https://github.com/pypeclub/pype/issues/664) + +**Fixed bugs:** + +- Wrong thumbnail file was picked when publishing sequence in standalone publisher [\#703](https://github.com/pypeclub/pype/pull/703) +- Fix: Burnin data pass and FFmpeg tool check [\#701](https://github.com/pypeclub/pype/pull/701) + +## [2.13.4](https://github.com/pypeclub/pype/tree/2.13.4) (2020-11-09) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.13.3...2.13.4) + +**Enhancements:** + +- AfterEffects integration with Websocket [\#663](https://github.com/pypeclub/pype/issues/663) + +**Fixed bugs:** + +- Photoshop uhiding hidden layers [\#688](https://github.com/pypeclub/pype/issues/688) +- \#688 - Fix publishing hidden layers [\#692](https://github.com/pypeclub/pype/pull/692) + +**Closed issues:** + +- Nuke Favorite directories "shot dir" "project dir" - not working [\#684](https://github.com/pypeclub/pype/issues/684) + +**Merged pull requests:** + +- Nuke Favorite directories "shot dir" "project dir" - not working \#684 [\#685](https://github.com/pypeclub/pype/pull/685) + +## [2.13.3](https://github.com/pypeclub/pype/tree/2.13.3) (2020-11-03) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.13.2...2.13.3) + +**Enhancements:** + +- TV paint base integration [\#612](https://github.com/pypeclub/pype/issues/612) + +**Fixed bugs:** + +- Fix ffmpeg executable path with spaces [\#680](https://github.com/pypeclub/pype/pull/680) +- Hotfix: Added default version number [\#679](https://github.com/pypeclub/pype/pull/679) + +## [2.13.2](https://github.com/pypeclub/pype/tree/2.13.2) (2020-10-28) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.13.1...2.13.2) + +**Fixed bugs:** + +- Nuke: wrong conditions when fixing legacy write nodes [\#665](https://github.com/pypeclub/pype/pull/665) + +## [2.13.1](https://github.com/pypeclub/pype/tree/2.13.1) (2020-10-23) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.13.0...2.13.1) + +**Enhancements:** + +- move maya look assigner to pype menu [\#292](https://github.com/pypeclub/pype/issues/292) + +**Fixed bugs:** + +- Layer name is not propagating to metadata in Photoshop [\#654](https://github.com/pypeclub/pype/issues/654) +- Loader in Photoshop fails with "can't set attribute" [\#650](https://github.com/pypeclub/pype/issues/650) +- Nuke Load mp4 wrong frame range [\#661](https://github.com/pypeclub/pype/issues/661) +- Hiero: Review video file adding one frame to the end [\#659](https://github.com/pypeclub/pype/issues/659) + +## [2.13.0](https://github.com/pypeclub/pype/tree/2.13.0) (2020-10-18) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.12.5...2.13.0) + +**Enhancements:** + +- Deadline Output Folder [\#636](https://github.com/pypeclub/pype/issues/636) +- Nuke Camera Loader [\#565](https://github.com/pypeclub/pype/issues/565) +- Deadline publish job shows publishing output folder [\#649](https://github.com/pypeclub/pype/pull/649) +- Get latest version in lib [\#642](https://github.com/pypeclub/pype/pull/642) +- Improved publishing of multiple representation from SP [\#638](https://github.com/pypeclub/pype/pull/638) +- Launch TvPaint shot work file from within Ftrack [\#631](https://github.com/pypeclub/pype/pull/631) +- Add mp4 support for RV action. [\#628](https://github.com/pypeclub/pype/pull/628) +- Maya: allow renders to have version synced with workfile [\#618](https://github.com/pypeclub/pype/pull/618) +- Renaming nukestudio host folder to hiero [\#617](https://github.com/pypeclub/pype/pull/617) +- Harmony: More efficient publishing [\#615](https://github.com/pypeclub/pype/pull/615) +- Ftrack server action improvement [\#608](https://github.com/pypeclub/pype/pull/608) +- Deadline user defaults to pype username if present [\#607](https://github.com/pypeclub/pype/pull/607) +- Standalone publisher now has icon [\#606](https://github.com/pypeclub/pype/pull/606) +- Nuke render write targeting knob improvement [\#603](https://github.com/pypeclub/pype/pull/603) +- Animated pyblish gui [\#602](https://github.com/pypeclub/pype/pull/602) +- Maya: Deadline - make use of asset dependencies optional [\#591](https://github.com/pypeclub/pype/pull/591) +- Nuke: Publishing, loading and updating alembic cameras [\#575](https://github.com/pypeclub/pype/pull/575) +- Maya: add look assigner to pype menu even if scriptsmenu is not available [\#573](https://github.com/pypeclub/pype/pull/573) +- Store task types in the database [\#572](https://github.com/pypeclub/pype/pull/572) +- Maya: Tiled EXRs to scanline EXRs render option [\#512](https://github.com/pypeclub/pype/pull/512) +- Fusion basic integration [\#452](https://github.com/pypeclub/pype/pull/452) + +**Fixed bugs:** + +- Burnin script did not propagate ffmpeg output [\#640](https://github.com/pypeclub/pype/issues/640) +- Pyblish-pype spacer in terminal wasn't transparent [\#646](https://github.com/pypeclub/pype/pull/646) +- Lib subprocess without logger [\#645](https://github.com/pypeclub/pype/pull/645) +- Nuke: prevent crash if we only have single frame in sequence [\#644](https://github.com/pypeclub/pype/pull/644) +- Burnin script logs better output [\#641](https://github.com/pypeclub/pype/pull/641) +- Missing audio on farm submission. [\#639](https://github.com/pypeclub/pype/pull/639) +- review from imagesequence error [\#633](https://github.com/pypeclub/pype/pull/633) +- Hiero: wrong order of fps clip instance data collecting [\#627](https://github.com/pypeclub/pype/pull/627) +- Add source for review instances. [\#625](https://github.com/pypeclub/pype/pull/625) +- Task processing in event sync [\#623](https://github.com/pypeclub/pype/pull/623) +- sync to avalon doesn t remove renamed task [\#619](https://github.com/pypeclub/pype/pull/619) +- Intent publish setting wasn't working with default value [\#562](https://github.com/pypeclub/pype/pull/562) +- Maya: Updating a look where the shader name changed, leaves the geo without a shader [\#514](https://github.com/pypeclub/pype/pull/514) + +**Merged pull requests:** + +- Avalon module without Qt [\#581](https://github.com/pypeclub/pype/pull/581) +- Ftrack module without Qt [\#577](https://github.com/pypeclub/pype/pull/577) + +## [2.12.5](https://github.com/pypeclub/pype/tree/2.12.5) (2020-10-14) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.12.4...2.12.5) + +**Enhancements:** + +- Launch TvPaint shot work file from within Ftrack [\#629](https://github.com/pypeclub/pype/issues/629) + +**Merged pull requests:** + +- Harmony: Disable application launch logic [\#637](https://github.com/pypeclub/pype/pull/637) + +## [2.12.4](https://github.com/pypeclub/pype/tree/2.12.4) (2020-10-08) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.12.3...2.12.4) + +**Enhancements:** + +- convert nukestudio to hiero host [\#616](https://github.com/pypeclub/pype/issues/616) +- Fusion basic integration [\#451](https://github.com/pypeclub/pype/issues/451) + +**Fixed bugs:** + +- Sync to avalon doesn't remove renamed task [\#605](https://github.com/pypeclub/pype/issues/605) +- NukeStudio: FPS collecting into clip instances [\#624](https://github.com/pypeclub/pype/pull/624) + +**Merged pull requests:** + +- NukeStudio: small fixes [\#622](https://github.com/pypeclub/pype/pull/622) +- NukeStudio: broken order of plugins [\#620](https://github.com/pypeclub/pype/pull/620) + +## [2.12.3](https://github.com/pypeclub/pype/tree/2.12.3) (2020-10-06) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.12.2...2.12.3) + +**Enhancements:** + +- Nuke Publish Camera [\#567](https://github.com/pypeclub/pype/issues/567) +- Harmony: open xstage file no matter of its name [\#526](https://github.com/pypeclub/pype/issues/526) +- Stop integration of unwanted data [\#387](https://github.com/pypeclub/pype/issues/387) +- Move avalon-launcher functionality to pype [\#229](https://github.com/pypeclub/pype/issues/229) +- avalon workfiles api [\#214](https://github.com/pypeclub/pype/issues/214) +- Store task types [\#180](https://github.com/pypeclub/pype/issues/180) +- Avalon Mongo Connection split [\#136](https://github.com/pypeclub/pype/issues/136) +- nk camera workflow [\#71](https://github.com/pypeclub/pype/issues/71) +- Hiero integration added [\#590](https://github.com/pypeclub/pype/pull/590) +- Anatomy instance data collection is substantially faster for many instances [\#560](https://github.com/pypeclub/pype/pull/560) + +**Fixed bugs:** + +- test issue [\#596](https://github.com/pypeclub/pype/issues/596) +- Harmony: empty scene contamination [\#583](https://github.com/pypeclub/pype/issues/583) +- Edit publishing in SP doesn't respect shot selection for publishing [\#542](https://github.com/pypeclub/pype/issues/542) +- Pathlib breaks compatibility with python2 hosts [\#281](https://github.com/pypeclub/pype/issues/281) +- Updating a look where the shader name changed leaves the geo without a shader [\#237](https://github.com/pypeclub/pype/issues/237) +- Better error handling [\#84](https://github.com/pypeclub/pype/issues/84) +- Harmony: function signature [\#609](https://github.com/pypeclub/pype/pull/609) +- Nuke: gizmo publishing error [\#594](https://github.com/pypeclub/pype/pull/594) +- Harmony: fix clashing namespace of called js functions [\#584](https://github.com/pypeclub/pype/pull/584) +- Maya: fix maya scene type preset exception [\#569](https://github.com/pypeclub/pype/pull/569) + +**Closed issues:** + +- Nuke Gizmo publishing [\#597](https://github.com/pypeclub/pype/issues/597) +- nuke gizmo publishing error [\#592](https://github.com/pypeclub/pype/issues/592) +- Publish EDL [\#579](https://github.com/pypeclub/pype/issues/579) +- Publish render from SP [\#576](https://github.com/pypeclub/pype/issues/576) +- rename ftrack custom attribute group to `pype` [\#184](https://github.com/pypeclub/pype/issues/184) + +**Merged pull requests:** + +- Audio file existence check [\#614](https://github.com/pypeclub/pype/pull/614) +- NKS small fixes [\#587](https://github.com/pypeclub/pype/pull/587) +- Standalone publisher editorial plugins interfering [\#580](https://github.com/pypeclub/pype/pull/580) + +## [2.12.2](https://github.com/pypeclub/pype/tree/2.12.2) (2020-09-25) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.12.1...2.12.2) + +**Enhancements:** + +- pype config GUI [\#241](https://github.com/pypeclub/pype/issues/241) + +**Fixed bugs:** + +- Harmony: Saving heavy scenes will crash [\#507](https://github.com/pypeclub/pype/issues/507) +- Extract review a representation name with `\*\_burnin` [\#388](https://github.com/pypeclub/pype/issues/388) +- Hierarchy data was not considering active isntances [\#551](https://github.com/pypeclub/pype/pull/551) + +## [2.12.1](https://github.com/pypeclub/pype/tree/2.12.1) (2020-09-15) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.12.0...2.12.1) + +**Fixed bugs:** + +- Pype: changelog.md is outdated [\#503](https://github.com/pypeclub/pype/issues/503) +- dependency security alert ! [\#484](https://github.com/pypeclub/pype/issues/484) +- Maya: RenderSetup is missing update [\#106](https://github.com/pypeclub/pype/issues/106) +- \ extract effects creates new instance [\#78](https://github.com/pypeclub/pype/issues/78) + +## [2.12.0](https://github.com/pypeclub/pype/tree/2.12.0) (2020-09-10) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.8...2.12.0) + +**Enhancements:** + +- Less mongo connections [\#509](https://github.com/pypeclub/pype/pull/509) +- Nuke: adding image loader [\#499](https://github.com/pypeclub/pype/pull/499) +- Move launcher window to top if launcher action is clicked [\#450](https://github.com/pypeclub/pype/pull/450) +- Maya: better tile rendering support in Pype [\#446](https://github.com/pypeclub/pype/pull/446) +- Implementation of non QML launcher [\#443](https://github.com/pypeclub/pype/pull/443) +- Optional skip review on renders. [\#441](https://github.com/pypeclub/pype/pull/441) +- Ftrack: Option to push status from task to latest version [\#440](https://github.com/pypeclub/pype/pull/440) +- Properly containerize image plane loads. [\#434](https://github.com/pypeclub/pype/pull/434) +- Option to keep the review files. [\#426](https://github.com/pypeclub/pype/pull/426) +- Isolate view on instance members. [\#425](https://github.com/pypeclub/pype/pull/425) +- Maya: Publishing of tile renderings on Deadline [\#398](https://github.com/pypeclub/pype/pull/398) +- Feature/little bit better logging gui [\#383](https://github.com/pypeclub/pype/pull/383) + +**Fixed bugs:** + +- Maya: Fix tile order for Draft Tile Assembler [\#511](https://github.com/pypeclub/pype/pull/511) +- Remove extra dash [\#501](https://github.com/pypeclub/pype/pull/501) +- Fix: strip dot from repre names in single frame renders [\#498](https://github.com/pypeclub/pype/pull/498) +- Better handling of destination during integrating [\#485](https://github.com/pypeclub/pype/pull/485) +- Fix: allow thumbnail creation for single frame renders [\#460](https://github.com/pypeclub/pype/pull/460) +- added missing argument to launch\_application in ftrack app handler [\#453](https://github.com/pypeclub/pype/pull/453) +- Burnins: Copy bit rate of input video to match quality. [\#448](https://github.com/pypeclub/pype/pull/448) +- Standalone publisher is now independent from tray [\#442](https://github.com/pypeclub/pype/pull/442) +- Bugfix/empty enumerator attributes [\#436](https://github.com/pypeclub/pype/pull/436) +- Fixed wrong order of "other" category collapssing in publisher [\#435](https://github.com/pypeclub/pype/pull/435) +- Multiple reviews where being overwritten to one. [\#424](https://github.com/pypeclub/pype/pull/424) +- Cleanup plugin fail on instances without staging dir [\#420](https://github.com/pypeclub/pype/pull/420) +- deprecated -intra parameter in ffmpeg to new `-g` [\#417](https://github.com/pypeclub/pype/pull/417) +- Delivery action can now work with entered path [\#397](https://github.com/pypeclub/pype/pull/397) + +**Merged pull requests:** + +- Review on instance.data [\#473](https://github.com/pypeclub/pype/pull/473) + +## [2.11.8](https://github.com/pypeclub/pype/tree/2.11.8) (2020-08-27) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.7...2.11.8) + +**Enhancements:** + +- DWAA support for Maya [\#382](https://github.com/pypeclub/pype/issues/382) +- Isolate View on Playblast [\#367](https://github.com/pypeclub/pype/issues/367) +- Maya: Tile rendering [\#297](https://github.com/pypeclub/pype/issues/297) +- single pype instance running [\#47](https://github.com/pypeclub/pype/issues/47) +- PYPE-649: projects don't guarantee backwards compatible environment [\#8](https://github.com/pypeclub/pype/issues/8) +- PYPE-663: separate venv for each deployed version [\#7](https://github.com/pypeclub/pype/issues/7) + +**Fixed bugs:** + +- pyblish pype - other group is collapsed before plugins are done [\#431](https://github.com/pypeclub/pype/issues/431) +- Alpha white edges in harmony on PNGs [\#412](https://github.com/pypeclub/pype/issues/412) +- harmony image loader picks wrong representations [\#404](https://github.com/pypeclub/pype/issues/404) +- Clockify crash when response contain symbol not allowed by UTF-8 [\#81](https://github.com/pypeclub/pype/issues/81) + +## [2.11.7](https://github.com/pypeclub/pype/tree/2.11.7) (2020-08-21) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.6...2.11.7) + +**Fixed bugs:** + +- Clean Up Baked Movie [\#369](https://github.com/pypeclub/pype/issues/369) +- celaction last workfile [\#459](https://github.com/pypeclub/pype/pull/459) + +## [2.11.6](https://github.com/pypeclub/pype/tree/2.11.6) (2020-08-18) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.5...2.11.6) + +**Enhancements:** + +- publisher app [\#56](https://github.com/pypeclub/pype/issues/56) + +## [2.11.5](https://github.com/pypeclub/pype/tree/2.11.5) (2020-08-13) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.4...2.11.5) + +**Enhancements:** + +- Switch from master to equivalent [\#220](https://github.com/pypeclub/pype/issues/220) +- Standalone publisher now only groups sequence if the extension is known [\#439](https://github.com/pypeclub/pype/pull/439) + +**Fixed bugs:** + +- Logs have been disable for editorial by default to speed up publishing [\#433](https://github.com/pypeclub/pype/pull/433) +- additional fixes for celaction [\#430](https://github.com/pypeclub/pype/pull/430) +- Harmony: invalid variable scope in validate scene settings [\#428](https://github.com/pypeclub/pype/pull/428) +- new representation name for audio was not accepted [\#427](https://github.com/pypeclub/pype/pull/427) + +## [2.11.4](https://github.com/pypeclub/pype/tree/2.11.4) (2020-08-10) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.3...2.11.4) + +**Enhancements:** + +- WebSocket server [\#135](https://github.com/pypeclub/pype/issues/135) +- standalonepublisher: editorial family features expansion \[master branch\] [\#411](https://github.com/pypeclub/pype/pull/411) + +## [2.11.3](https://github.com/pypeclub/pype/tree/2.11.3) (2020-08-04) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.2...2.11.3) + +**Fixed bugs:** + +- Harmony: publishing performance issues [\#408](https://github.com/pypeclub/pype/pull/408) + +## [2.11.2](https://github.com/pypeclub/pype/tree/2.11.2) (2020-07-31) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.1...2.11.2) + +**Fixed bugs:** + +- Ftrack to Avalon bug [\#406](https://github.com/pypeclub/pype/issues/406) + +## [2.11.1](https://github.com/pypeclub/pype/tree/2.11.1) (2020-07-29) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.0...2.11.1) + +**Merged pull requests:** + +- Celaction: metadata json folder fixes on path [\#393](https://github.com/pypeclub/pype/pull/393) +- CelAction - version up method taken fro pype.lib [\#391](https://github.com/pypeclub/pype/pull/391) + ## 2.11.0 ## @@ -430,3 +787,6 @@ A large cleanup release. Most of the change are under the hood. - work directory was sometimes not being created correctly - major pype.lib cleanup. Removing of unused functions, merging those that were doing the same and general house cleaning. - _(avalon)_ subsets in maya 2019 weren't behaving correctly in the outliner + + +\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* diff --git a/pype/__init__.py b/pype/__init__.py index bae6f2f9b1..d88299693e 100644 --- a/pype/__init__.py +++ b/pype/__init__.py @@ -72,16 +72,19 @@ def patched_discover(superclass): elif superclass.__name__.split(".")[-1] == "Creator": plugin_type = "create" - print(">>> trying to find presets for {}:{} ...".format(host, plugin_type)) + print(">>> Finding presets for {}:{} ...".format(host, plugin_type)) try: - config_data = config.get_presets()['plugins'][host][plugin_type] + settings = ( + get_project_settings(os.environ['AVALON_PROJECT']) + [host][plugin_type] + ) except KeyError: print("*** no presets found.") else: for plugin in plugins: - if plugin.__name__ in config_data: + if plugin.__name__ in settings: print(">>> We have preset for {}".format(plugin.__name__)) - for option, value in config_data[plugin.__name__].items(): + for option, value in settings[plugin.__name__].items(): if option == "enabled" and value is False: setattr(plugin, "active", False) print(" - is disabled by preset") @@ -130,6 +133,7 @@ def install(): anatomy.set_root_environments() avalon.register_root(anatomy.roots) # apply monkey patched discover to original one + log.info("Patching discovery") avalon.discover = patched_discover diff --git a/pype/api.py b/pype/api.py index 3b906a27ff..a2b4f22e72 100644 --- a/pype/api.py +++ b/pype/api.py @@ -1,7 +1,9 @@ from .settings import ( - system_settings, - project_settings, - environments + get_system_settings, + get_project_settings, + get_current_project_settings, + get_anatomy_settings, + get_environments ) from .lib import ( PypeLogger, @@ -52,9 +54,11 @@ from .lib import _subprocess as subprocess Logger = PypeLogger __all__ = [ - "system_settings", - "project_settings", - "environments", + "get_system_settings", + "get_project_settings", + "get_current_project_settings", + "get_anatomy_settings", + "get_environments", "PypeLogger", "Logger", diff --git a/pype/hooks/aftereffects/pre_launch_args.py b/pype/hooks/aftereffects/pre_launch_args.py new file mode 100644 index 0000000000..e39247b983 --- /dev/null +++ b/pype/hooks/aftereffects/pre_launch_args.py @@ -0,0 +1,45 @@ +import os + +from pype.lib import PreLaunchHook + + +class AfterEffectsPrelaunchHook(PreLaunchHook): + """Launch arguments preparation. + + Hook add python executable and execute python script of AfterEffects + implementation before AfterEffects executable. + """ + app_groups = ["aftereffects"] + + def execute(self): + # Pop tvpaint executable + aftereffects_executable = self.launch_context.launch_args.pop(0) + + # Pop rest of launch arguments - There should not be other arguments! + remainders = [] + while self.launch_context.launch_args: + remainders.append(self.launch_context.launch_args.pop(0)) + + new_launch_args = [ + self.python_executable(), + "-c", + ( + "import avalon.aftereffects;" + "avalon.aftereffects.launch(\"{}\")" + ).format(aftereffects_executable) + ] + + # Append as whole list as these areguments should not be separated + self.launch_context.launch_args.append(new_launch_args) + + if remainders: + self.log.warning(( + "There are unexpected launch arguments " + "in AfterEffects launch. {}" + ).format(str(remainders))) + self.launch_context.launch_args.extend(remainders) + + def python_executable(self): + """Should lead to python executable.""" + # TODO change in Pype 3 + return os.environ["PYPE_PYTHON_EXE"] diff --git a/pype/hooks/celaction/pre_celaction_registers.py b/pype/hooks/celaction/pre_celaction_registers.py new file mode 100644 index 0000000000..04ecf82c5c --- /dev/null +++ b/pype/hooks/celaction/pre_celaction_registers.py @@ -0,0 +1,125 @@ +import os +import shutil +import winreg +from pype.lib import PreLaunchHook +from pype.hosts import celaction + + +class CelactionPrelaunchHook(PreLaunchHook): + """ + This hook will check if current workfile path has Unreal + project inside. IF not, it initialize it and finally it pass + path to the project by environment variable to Unreal launcher + shell script. + """ + workfile_ext = "scn" + app_groups = ["celaction"] + platforms = ["windows"] + + def execute(self): + # Add workfile path to launch arguments + workfile_path = self.workfile_path() + if workfile_path: + self.launch_context.launch_args.append(workfile_path) + + project_name = self.data["project_name"] + asset_name = self.data["asset_name"] + task_name = self.data["task_name"] + + # get publish version of celaction + app = "celaction_publish" + + # setting output parameters + path = r"Software\CelAction\CelAction2D\User Settings" + winreg.CreateKey(winreg.HKEY_CURRENT_USER, path) + hKey = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + "Software\\CelAction\\CelAction2D\\User Settings", 0, + winreg.KEY_ALL_ACCESS) + + # TODO: change to root path and pyblish standalone to premiere way + pype_root_path = os.getenv("PYPE_SETUP_PATH") + path = os.path.join(pype_root_path, "pype.bat") + + winreg.SetValueEx(hKey, "SubmitAppTitle", 0, winreg.REG_SZ, path) + + parameters = [ + "launch", + f"--app {app}", + f"--project {project_name}", + f"--asset {asset_name}", + f"--task {task_name}", + "--currentFile \\\"\"*SCENE*\"\\\"", + "--chunk 10", + "--frameStart *START*", + "--frameEnd *END*", + "--resolutionWidth *X*", + "--resolutionHeight *Y*", + # "--programDir \"'*PROGPATH*'\"" + ] + winreg.SetValueEx(hKey, "SubmitParametersTitle", 0, winreg.REG_SZ, + " ".join(parameters)) + + # setting resolution parameters + path = r"Software\CelAction\CelAction2D\User Settings\Dialogs" + path += r"\SubmitOutput" + winreg.CreateKey(winreg.HKEY_CURRENT_USER, path) + hKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0, + winreg.KEY_ALL_ACCESS) + winreg.SetValueEx(hKey, "SaveScene", 0, winreg.REG_DWORD, 1) + winreg.SetValueEx(hKey, "CustomX", 0, winreg.REG_DWORD, 1920) + winreg.SetValueEx(hKey, "CustomY", 0, winreg.REG_DWORD, 1080) + + # making sure message dialogs don't appear when overwriting + path = r"Software\CelAction\CelAction2D\User Settings\Messages" + path += r"\OverwriteScene" + winreg.CreateKey(winreg.HKEY_CURRENT_USER, path) + hKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0, + winreg.KEY_ALL_ACCESS) + winreg.SetValueEx(hKey, "Result", 0, winreg.REG_DWORD, 6) + winreg.SetValueEx(hKey, "Valid", 0, winreg.REG_DWORD, 1) + + path = r"Software\CelAction\CelAction2D\User Settings\Messages" + path += r"\SceneSaved" + winreg.CreateKey(winreg.HKEY_CURRENT_USER, path) + hKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0, + winreg.KEY_ALL_ACCESS) + winreg.SetValueEx(hKey, "Result", 0, winreg.REG_DWORD, 1) + winreg.SetValueEx(hKey, "Valid", 0, winreg.REG_DWORD, 1) + + def workfile_path(self): + workfile_path = self.data["last_workfile_path"] + + # copy workfile from template if doesnt exist any on path + if not os.path.exists(workfile_path): + # TODO add ability to set different template workfile path via + # settings + pype_celaction_dir = os.path.dirname( + os.path.abspath(celaction.__file__) + ) + template_path = os.path.join( + pype_celaction_dir, + "celaction_template_scene.scn" + ) + + if not os.path.exists(template_path): + self.log.warning( + "Couldn't find workfile template file in {}".format( + template_path + ) + ) + return + + self.log.info( + f"Creating workfile from template: \"{template_path}\"" + ) + + # Copy template workfile to new destinantion + shutil.copy2( + os.path.normpath(template_path), + os.path.normpath(workfile_path) + ) + + self.log.info(f"Workfile to open: \"{workfile_path}\"") + + return workfile_path diff --git a/pype/hooks/fusion/pre_fusion_setup.py b/pype/hooks/fusion/pre_fusion_setup.py new file mode 100644 index 0000000000..d4402e9a04 --- /dev/null +++ b/pype/hooks/fusion/pre_fusion_setup.py @@ -0,0 +1,50 @@ +import os +import importlib +from pype.lib import PreLaunchHook +from pype.hosts.fusion import utils + + +class FusionPrelaunch(PreLaunchHook): + """ + This hook will check if current workfile path has Fusion + project inside. + """ + app_groups = ["fusion"] + + def execute(self): + # making sure pyton 3.6 is installed at provided path + py36_dir = os.path.normpath(self.env.get("PYTHON36", "")) + assert os.path.isdir(py36_dir), ( + "Python 3.6 is not installed at the provided folder path. Either " + "make sure the `environments\resolve.json` is having correctly " + "set `PYTHON36` or make sure Python 3.6 is installed " + f"in given path. \nPYTHON36E: `{py36_dir}`" + ) + self.log.info(f"Path to Fusion Python folder: `{py36_dir}`...") + self.env["PYTHON36"] = py36_dir + + # setting utility scripts dir for scripts syncing + us_dir = os.path.normpath( + self.env.get("FUSION_UTILITY_SCRIPTS_DIR", "") + ) + assert os.path.isdir(us_dir), ( + "Fusion utility script dir does not exists. Either make sure " + "the `environments\fusion.json` is having correctly set " + "`FUSION_UTILITY_SCRIPTS_DIR` or reinstall DaVinci Resolve. \n" + f"FUSION_UTILITY_SCRIPTS_DIR: `{us_dir}`" + ) + + try: + __import__("avalon.fusion") + __import__("pyblish") + + except ImportError: + self.log.warning( + "pyblish: Could not load Fusion integration.", + exc_info=True + ) + + else: + # Resolve Setup integration + importlib.reload(utils) + utils.setup(self.env) diff --git a/pype/hooks/global/post_ftrack_changes.py b/pype/hooks/global/post_ftrack_changes.py new file mode 100644 index 0000000000..4dc45f5419 --- /dev/null +++ b/pype/hooks/global/post_ftrack_changes.py @@ -0,0 +1,197 @@ +import os + +import ftrack_api +from pype.api import get_project_settings +from pype.lib import PostLaunchHook + + +class PostFtrackHook(PostLaunchHook): + order = None + + def execute(self): + project_name = self.data.get("project_name") + asset_name = self.data.get("asset_name") + task_name = self.data.get("task_name") + + missing_context_keys = set() + if not project_name: + missing_context_keys.add("project_name") + if not asset_name: + missing_context_keys.add("asset_name") + if not task_name: + missing_context_keys.add("task_name") + + if missing_context_keys: + missing_keys_str = ", ".join([ + "\"{}\"".format(key) for key in missing_context_keys + ]) + self.log.debug("Hook {} skipped. Missing data keys: {}".format( + self.__class__.__name__, missing_keys_str + )) + return + + required_keys = ("FTRACK_SERVER", "FTRACK_API_USER", "FTRACK_API_KEY") + for key in required_keys: + if not os.environ.get(key): + self.log.debug(( + "Missing required environment \"{}\"" + " for Ftrack after launch procedure." + ).format(key)) + return + + try: + session = ftrack_api.Session(auto_connect_event_hub=True) + self.log.debug("Ftrack session created") + except Exception: + self.log.warning("Couldn't create Ftrack session") + return + + try: + entity = self.find_ftrack_task_entity( + session, project_name, asset_name, task_name + ) + if entity: + self.ftrack_status_change(session, entity, project_name) + self.start_timer(session, entity, ftrack_api) + except Exception: + self.log.warning( + "Couldn't finish Ftrack procedure.", exc_info=True + ) + return + + finally: + session.close() + + def find_ftrack_task_entity( + self, session, project_name, asset_name, task_name + ): + project_entity = session.query( + "Project where full_name is \"{}\"".format(project_name) + ).first() + if not project_entity: + self.log.warning( + "Couldn't find project \"{}\" in Ftrack.".format(project_name) + ) + return + + potential_task_entities = session.query(( + "TypedContext where parent.name is \"{}\" and project_id is \"{}\"" + ).format(asset_name, project_entity["id"])).all() + filtered_entities = [] + for _entity in potential_task_entities: + if ( + _entity.entity_type.lower() == "task" + and _entity["name"] == task_name + ): + filtered_entities.append(_entity) + + if not filtered_entities: + self.log.warning(( + "Couldn't find task \"{}\" under parent \"{}\" in Ftrack." + ).format(task_name, asset_name)) + return + + if len(filtered_entities) > 1: + self.log.warning(( + "Found more than one task \"{}\"" + " under parent \"{}\" in Ftrack." + ).format(task_name, asset_name)) + return + + return filtered_entities[0] + + def ftrack_status_change(self, session, entity, project_name): + project_settings = get_project_settings(project_name) + status_update = project_settings["ftrack"]["events"]["status_update"] + if not status_update["enabled"]: + self.log.debug( + "Status changes are disabled for project \"{}\"".format( + project_name + ) + ) + return + + status_mapping = status_update["mapping"] + if not status_mapping: + self.log.warning( + "Project \"{}\" does not have set status changes.".format( + project_name + ) + ) + return + + actual_status = entity["status"]["name"].lower() + already_tested = set() + ent_path = "/".join( + [ent["name"] for ent in entity["link"]] + ) + while True: + next_status_name = None + for key, value in status_mapping.items(): + if key in already_tested: + continue + if actual_status in value or "__any__" in value: + if key != "__ignore__": + next_status_name = key + already_tested.add(key) + break + already_tested.add(key) + + if next_status_name is None: + break + + try: + query = "Status where name is \"{}\"".format( + next_status_name + ) + status = session.query(query).one() + + entity["status"] = status + session.commit() + self.log.debug("Changing status to \"{}\" <{}>".format( + next_status_name, ent_path + )) + break + + except Exception: + session.rollback() + msg = ( + "Status \"{}\" in presets wasn't found" + " on Ftrack entity type \"{}\"" + ).format(next_status_name, entity.entity_type) + self.log.warning(msg) + + def start_timer(self, session, entity, _ftrack_api): + """Start Ftrack timer on task from context.""" + self.log.debug("Triggering timer start.") + + user_entity = session.query("User where username is \"{}\"".format( + os.environ["FTRACK_API_USER"] + )).first() + if not user_entity: + self.log.warning( + "Couldn't find user with username \"{}\" in Ftrack".format( + os.environ["FTRACK_API_USER"] + ) + ) + return + + source = { + "user": { + "id": user_entity["id"], + "username": user_entity["username"] + } + } + event_data = { + "actionIdentifier": "start.timer", + "selection": [{"entityId": entity["id"], "entityType": "task"}] + } + session.event_hub.publish( + _ftrack_api.event.base.Event( + topic="ftrack.action.launch", + data=event_data, + source=source + ), + on_error="ignore" + ) + self.log.debug("Timer start triggered successfully.") diff --git a/pype/hooks/global/pre_global_host_data.py b/pype/hooks/global/pre_global_host_data.py new file mode 100644 index 0000000000..4910d08010 --- /dev/null +++ b/pype/hooks/global/pre_global_host_data.py @@ -0,0 +1,354 @@ +import os +import re +import json +import getpass +import copy + +from pype.api import ( + Anatomy, + get_project_settings +) +from pype.lib import ( + env_value_to_bool, + PreLaunchHook, + ApplicationLaunchFailed +) + +import acre +import avalon.api + + +class GlobalHostDataHook(PreLaunchHook): + order = -100 + + def execute(self): + """Prepare global objects to `data` that will be used for sure.""" + if not self.application.is_host: + self.log.info( + "Skipped hook {}. Application is not marked as host.".format( + self.__class__.__name__ + ) + ) + return + + self.prepare_global_data() + self.prepare_host_environments() + self.prepare_context_environments() + + def prepare_global_data(self): + """Prepare global objects to `data` that will be used for sure.""" + # Mongo documents + project_name = self.data.get("project_name") + if not project_name: + self.log.info( + "Skipping global data preparation." + " Key `project_name` was not found in launch context." + ) + return + + self.log.debug("Project name is set to \"{}\"".format(project_name)) + # Anatomy + self.data["anatomy"] = Anatomy(project_name) + + # Mongo connection + dbcon = avalon.api.AvalonMongoDB() + dbcon.Session["AVALON_PROJECT"] = project_name + dbcon.install() + + self.data["dbcon"] = dbcon + + # Project document + project_doc = dbcon.find_one({"type": "project"}) + self.data["project_doc"] = project_doc + + asset_name = self.data.get("asset_name") + if not asset_name: + self.log.warning( + "Asset name was not set. Skipping asset document query." + ) + return + + asset_doc = dbcon.find_one({ + "type": "asset", + "name": asset_name + }) + self.data["asset_doc"] = asset_doc + + def _merge_env(self, env, current_env): + """Modified function(merge) from acre module.""" + result = current_env.copy() + for key, value in env.items(): + # Keep missing keys by not filling `missing` kwarg + value = acre.lib.partial_format(value, data=current_env) + result[key] = value + return result + + def prepare_host_environments(self): + """Modify launch environments based on launched app and context.""" + # Keys for getting environments + env_keys = [self.app_group, self.app_name] + + asset_doc = self.data.get("asset_doc") + if asset_doc: + # Add tools environments + for key in asset_doc["data"].get("tools_env") or []: + tool = self.manager.tools.get(key) + if tool: + if tool.group_name not in env_keys: + env_keys.append(tool.group_name) + + if tool.name not in env_keys: + env_keys.append(tool.name) + + self.log.debug( + "Finding environment groups for keys: {}".format(env_keys) + ) + + settings_env = self.data["settings_env"] + env_values = {} + for env_key in env_keys: + _env_values = settings_env.get(env_key) + if not _env_values: + continue + + # Choose right platform + tool_env = acre.parse(_env_values) + # Merge dictionaries + env_values = self._merge_env(tool_env, env_values) + + final_env = self._merge_env( + acre.compute(env_values), self.launch_context.env + ) + + # Update env + self.launch_context.env.update(final_env) + + def prepare_context_environments(self): + """Modify launch environemnts with context data for launched host.""" + # Context environments + project_doc = self.data.get("project_doc") + asset_doc = self.data.get("asset_doc") + task_name = self.data.get("task_name") + if ( + not project_doc + or not asset_doc + or not task_name + ): + self.log.info( + "Skipping context environments preparation." + " Launch context does not contain required data." + ) + return + + workdir_data = self._prepare_workdir_data( + project_doc, asset_doc, task_name + ) + self.data["workdir_data"] = workdir_data + + hierarchy = workdir_data["hierarchy"] + anatomy = self.data["anatomy"] + + try: + anatomy_filled = anatomy.format(workdir_data) + workdir = os.path.normpath(anatomy_filled["work"]["folder"]) + if not os.path.exists(workdir): + self.log.debug( + "Creating workdir folder: \"{}\"".format(workdir) + ) + os.makedirs(workdir) + + except Exception as exc: + raise ApplicationLaunchFailed( + "Error in anatomy.format: {}".format(str(exc)) + ) + + context_env = { + "AVALON_PROJECT": project_doc["name"], + "AVALON_ASSET": asset_doc["name"], + "AVALON_TASK": task_name, + "AVALON_APP": self.host_name, + "AVALON_APP_NAME": self.app_name, + "AVALON_HIERARCHY": hierarchy, + "AVALON_WORKDIR": workdir + } + self.log.debug( + "Context environemnts set:\n{}".format( + json.dumps(context_env, indent=4) + ) + ) + self.launch_context.env.update(context_env) + + self.prepare_last_workfile(workdir) + + def _prepare_workdir_data(self, project_doc, asset_doc, task_name): + hierarchy = "/".join(asset_doc["data"]["parents"]) + + data = { + "project": { + "name": project_doc["name"], + "code": project_doc["data"].get("code") + }, + "task": task_name, + "asset": asset_doc["name"], + "app": self.host_name, + "hierarchy": hierarchy + } + return data + + def prepare_last_workfile(self, workdir): + """last workfile workflow preparation. + + Function check if should care about last workfile workflow and tries + to find the last workfile. Both information are stored to `data` and + environments. + + Last workfile is filled always (with version 1) even if any workfile + exists yet. + + Args: + workdir (str): Path to folder where workfiles should be stored. + """ + _workdir_data = self.data.get("workdir_data") + if not _workdir_data: + self.log.info( + "Skipping last workfile preparation." + " Key `workdir_data` not filled." + ) + return + + workdir_data = copy.deepcopy(_workdir_data) + project_name = self.data["project_name"] + task_name = self.data["task_name"] + start_last_workfile = self.should_start_last_workfile( + project_name, self.host_name, task_name + ) + self.data["start_last_workfile"] = start_last_workfile + + # Store boolean as "0"(False) or "1"(True) + self.launch_context.env["AVALON_OPEN_LAST_WORKFILE"] = ( + str(int(bool(start_last_workfile))) + ) + + _sub_msg = "" if start_last_workfile else " not" + self.log.debug( + "Last workfile should{} be opened on start.".format(_sub_msg) + ) + + # Last workfile path + last_workfile_path = "" + extensions = avalon.api.HOST_WORKFILE_EXTENSIONS.get( + self.host_name + ) + if extensions: + anatomy = self.data["anatomy"] + # Find last workfile + file_template = anatomy.templates["work"]["file"] + workdir_data.update({ + "version": 1, + "user": os.environ.get("PYPE_USERNAME") or getpass.getuser(), + "ext": extensions[0] + }) + + last_workfile_path = avalon.api.last_workfile( + workdir, file_template, workdir_data, extensions, True + ) + + if os.path.exists(last_workfile_path): + self.log.debug(( + "Workfiles for launch context does not exists" + " yet but path will be set." + )) + self.log.debug( + "Setting last workfile path: {}".format(last_workfile_path) + ) + + self.launch_context.env["AVALON_LAST_WORKFILE"] = last_workfile_path + self.data["last_workfile_path"] = last_workfile_path + + def should_start_last_workfile(self, project_name, host_name, task_name): + """Define if host should start last version workfile if possible. + + Default output is `False`. Can be overriden with environment variable + `AVALON_OPEN_LAST_WORKFILE`, valid values without case sensitivity are + `"0", "1", "true", "false", "yes", "no"`. + + Args: + project_name (str): Name of project. + host_name (str): Name of host which is launched. In avalon's + application context it's value stored in app definition under + key `"application_dir"`. Is not case sensitive. + task_name (str): Name of task which is used for launching the host. + Task name is not case sensitive. + + Returns: + bool: True if host should start workfile. + + """ + + project_settings = ( + get_project_settings(project_name)['global']['tools']) + startup_presets = ( + project_settings['Workfiles']['last_workfile_on_startup']) + + if not startup_presets: + return default_output + + host_name_lowered = host_name.lower() + task_name_lowered = task_name.lower() + + max_points = 2 + matching_points = -1 + matching_item = None + for item in startup_presets: + hosts = item.get("hosts") or tuple() + tasks = item.get("tasks") or tuple() + + hosts_lowered = tuple(_host_name.lower() for _host_name in hosts) + # Skip item if has set hosts and current host is not in + if hosts_lowered and host_name_lowered not in hosts_lowered: + continue + + tasks_lowered = tuple(_task_name.lower() for _task_name in tasks) + # Skip item if has set tasks and current task is not in + if tasks_lowered: + task_match = False + for task_regex in self.compile_list_of_regexes(tasks_lowered): + if re.match(task_regex, task_name_lowered): + task_match = True + break + + if not task_match: + continue + + points = int(bool(hosts_lowered)) + int(bool(tasks_lowered)) + if points > matching_points: + matching_item = item + matching_points = points + + if matching_points == max_points: + break + + if matching_item is not None: + output = matching_item.get("enabled") + if output is None: + output = default_output + return output + return default_output + + @staticmethod + def compile_list_of_regexes(in_list): + """Convert strings in entered list to compiled regex objects.""" + regexes = list() + if not in_list: + return regexes + + for item in in_list: + if item: + try: + regexes.append(re.compile(item)) + except TypeError: + print(( + "Invalid type \"{}\" value \"{}\"." + " Expected string based object. Skipping." + ).format(str(type(item)), str(item))) + return regexes diff --git a/pype/hooks/harmony/pre_launch_args.py b/pype/hooks/harmony/pre_launch_args.py new file mode 100644 index 0000000000..70c05eb352 --- /dev/null +++ b/pype/hooks/harmony/pre_launch_args.py @@ -0,0 +1,44 @@ +import os + +from pype.lib import PreLaunchHook + + +class HarmonyPrelaunchHook(PreLaunchHook): + """Launch arguments preparation. + + Hook add python executable and execute python script of harmony + implementation before harmony executable. + """ + app_groups = ["harmony"] + + def execute(self): + # Pop tvpaint executable + harmony_executable = self.launch_context.launch_args.pop(0) + + # Pop rest of launch arguments - There should not be other arguments! + remainders = [] + while self.launch_context.launch_args: + remainders.append(self.launch_context.launch_args.pop(0)) + + new_launch_args = [ + self.python_executable(), + "-c", + ( + "import avalon.harmony;" + "avalon.harmony.launch(\"{}\")" + ).format(harmony_executable) + ] + + # Append as whole list as these areguments should not be separated + self.launch_context.launch_args.append(new_launch_args) + + if remainders: + self.log.warning(( + "There are unexpected launch arguments in Harmony launch. {}" + ).format(str(remainders))) + self.launch_context.launch_args.extend(remainders) + + def python_executable(self): + """Should lead to python executable.""" + # TODO change in Pype 3 + return os.environ["PYPE_PYTHON_EXE"] diff --git a/pype/hooks/hiero/pre_launch_args.py b/pype/hooks/hiero/pre_launch_args.py new file mode 100644 index 0000000000..6f5d0c0b00 --- /dev/null +++ b/pype/hooks/hiero/pre_launch_args.py @@ -0,0 +1,15 @@ +import os +from pype.lib import PreLaunchHook + + +class HieroLaunchArguments(PreLaunchHook): + order = 0 + app_groups = ["hiero"] + + def execute(self): + """Prepare suprocess launch arguments for Hiero.""" + # Add path to workfile to arguments + if self.data.get("start_last_workfile"): + last_workfile = self.data.get("last_workfile_path") + if os.path.exists(last_workfile): + self.launch_context.launch_args.append(last_workfile) diff --git a/pype/hooks/maya/pre_launch_args.py b/pype/hooks/maya/pre_launch_args.py new file mode 100644 index 0000000000..26b935ea01 --- /dev/null +++ b/pype/hooks/maya/pre_launch_args.py @@ -0,0 +1,16 @@ +import os +from pype.lib import PreLaunchHook + + +class MayaLaunchArguments(PreLaunchHook): + """Add path to last workfile to launch arguments.""" + order = 0 + app_groups = ["maya"] + + def execute(self): + """Prepare suprocess launch arguments for Maya.""" + # Add path to workfile to arguments + if self.data.get("start_last_workfile"): + last_workfile = self.data.get("last_workfile_path") + if os.path.exists(last_workfile): + self.launch_context.launch_args.append(last_workfile) diff --git a/pype/hooks/nukestudio/pre_launch_args.py b/pype/hooks/nukestudio/pre_launch_args.py new file mode 100644 index 0000000000..6056441042 --- /dev/null +++ b/pype/hooks/nukestudio/pre_launch_args.py @@ -0,0 +1,15 @@ +import os +from pype.lib import PreLaunchHook + + +class NukeStudioLaunchArguments(PreLaunchHook): + order = 0 + app_groups = ["nukestudio"] + + def execute(self): + """Prepare suprocess launch arguments for NukeStudio.""" + # Add path to workfile to arguments + if self.data.get("start_last_workfile"): + last_workfile = self.data.get("last_workfile_path") + if os.path.exists(last_workfile): + self.launch_context.launch_args.append(last_workfile) diff --git a/pype/hooks/nukex/pre_launch_args.py b/pype/hooks/nukex/pre_launch_args.py new file mode 100644 index 0000000000..979bfcce0b --- /dev/null +++ b/pype/hooks/nukex/pre_launch_args.py @@ -0,0 +1,15 @@ +import os +from pype.lib import PreLaunchHook + + +class NukeXLaunchArguments(PreLaunchHook): + order = 0 + app_groups = ["nukex"] + + def execute(self): + """Prepare suprocess launch arguments for NukeX.""" + # Add path to workfile to arguments + if self.data.get("start_last_workfile"): + last_workfile = self.data.get("last_workfile_path") + if os.path.exists(last_workfile): + self.launch_context.launch_args.append(last_workfile) diff --git a/pype/hooks/photoshop/pre_launch_args.py b/pype/hooks/photoshop/pre_launch_args.py new file mode 100644 index 0000000000..b13e7d1e0f --- /dev/null +++ b/pype/hooks/photoshop/pre_launch_args.py @@ -0,0 +1,44 @@ +import os + +from pype.lib import PreLaunchHook + + +class PhotoshopPrelaunchHook(PreLaunchHook): + """Launch arguments preparation. + + Hook add python executable and execute python script of photoshop + implementation before photoshop executable. + """ + app_groups = ["photoshop"] + + def execute(self): + # Pop tvpaint executable + photoshop_executable = self.launch_context.launch_args.pop(0) + + # Pop rest of launch arguments - There should not be other arguments! + remainders = [] + while self.launch_context.launch_args: + remainders.append(self.launch_context.launch_args.pop(0)) + + new_launch_args = [ + self.python_executable(), + "-c", + ( + "import avalon.photoshop;" + "avalon.photoshop.launch(\"{}\")" + ).format(photoshop_executable) + ] + + # Append as whole list as these areguments should not be separated + self.launch_context.launch_args.append(new_launch_args) + + if remainders: + self.log.warning(( + "There are unexpected launch arguments in Photoshop launch. {}" + ).format(str(remainders))) + self.launch_context.launch_args.extend(remainders) + + def python_executable(self): + """Should lead to python executable.""" + # TODO change in Pype 3 + return os.environ["PYPE_PYTHON_EXE"] diff --git a/pype/hooks/resolve/pre_resolve_setup.py b/pype/hooks/resolve/pre_resolve_setup.py new file mode 100644 index 0000000000..4f6d33c6eb --- /dev/null +++ b/pype/hooks/resolve/pre_resolve_setup.py @@ -0,0 +1,58 @@ +import os +import importlib +from pype.lib import PreLaunchHook +from pype.hosts.resolve import utils + + +class ResolvePrelaunch(PreLaunchHook): + """ + This hook will check if current workfile path has Resolve + project inside. IF not, it initialize it and finally it pass + path to the project by environment variable to Premiere launcher + shell script. + """ + app_groups = ["resolve"] + + def execute(self): + # making sure pyton 3.6 is installed at provided path + py36_dir = os.path.normpath(self.env.get("PYTHON36_RESOLVE", "")) + assert os.path.isdir(py36_dir), ( + "Python 3.6 is not installed at the provided folder path. Either " + "make sure the `environments\resolve.json` is having correctly " + "set `PYTHON36_RESOLVE` or make sure Python 3.6 is installed " + f"in given path. \nPYTHON36_RESOLVE: `{py36_dir}`" + ) + self.log.info(f"Path to Resolve Python folder: `{py36_dir}`...") + self.env["PYTHON36_RESOLVE"] = py36_dir + + # setting utility scripts dir for scripts syncing + us_dir = os.path.normpath( + self.env.get("RESOLVE_UTILITY_SCRIPTS_DIR", "") + ) + assert os.path.isdir(us_dir), ( + "Resolve utility script dir does not exists. Either make sure " + "the `environments\resolve.json` is having correctly set " + "`RESOLVE_UTILITY_SCRIPTS_DIR` or reinstall DaVinci Resolve. \n" + f"RESOLVE_UTILITY_SCRIPTS_DIR: `{us_dir}`" + ) + self.log.debug(f"-- us_dir: `{us_dir}`") + + # correctly format path for pre python script + pre_py_sc = os.path.normpath(self.env.get("PRE_PYTHON_SCRIPT", "")) + self.env["PRE_PYTHON_SCRIPT"] = pre_py_sc + self.log.debug(f"-- pre_py_sc: `{pre_py_sc}`...") + try: + __import__("pype.hosts.resolve") + __import__("pyblish") + + except ImportError: + self.log.warning( + "pyblish: Could not load Resolve integration.", + exc_info=True + ) + + else: + # Resolve Setup integration + importlib.reload(utils) + self.log.debug(f"-- utils.__file__: `{utils.__file__}`") + utils.setup(self.env) diff --git a/pype/hooks/tvpaint/pre_install_pywin.py b/pype/hooks/tvpaint/pre_install_pywin.py new file mode 100644 index 0000000000..ca9242c4c8 --- /dev/null +++ b/pype/hooks/tvpaint/pre_install_pywin.py @@ -0,0 +1,35 @@ +from pype.lib import ( + PreLaunchHook, + ApplicationLaunchFailed, + _subprocess +) + + +class PreInstallPyWin(PreLaunchHook): + """Hook makes sure there is installed python module pywin32 on windows.""" + # WARNING This hook will probably be deprecated in Pype 3 - kept for test + order = 10 + app_groups = ["tvpaint"] + platforms = ["windows"] + + def execute(self): + installed = False + try: + from win32com.shell import shell + self.log.debug("Python module `pywin32` already installed.") + installed = True + except Exception: + pass + + if installed: + return + + try: + output = _subprocess( + ["pip", "install", "pywin32==227"] + ) + self.log.debug("Pip install pywin32 output:\n{}'".format(output)) + except RuntimeError: + msg = "Installation of python module `pywin32` crashed." + self.log.warning(msg, exc_info=True) + raise ApplicationLaunchFailed(msg) diff --git a/pype/hooks/tvpaint/pre_launch_args.py b/pype/hooks/tvpaint/pre_launch_args.py new file mode 100644 index 0000000000..210b1e99c3 --- /dev/null +++ b/pype/hooks/tvpaint/pre_launch_args.py @@ -0,0 +1,103 @@ +import os +import shutil + +from pype.hosts import tvpaint +from pype.lib import PreLaunchHook + +import avalon + + +class TvpaintPrelaunchHook(PreLaunchHook): + """Launch arguments preparation. + + Hook add python executable and script path to tvpaint implementation before + tvpaint executable and add last workfile path to launch arguments. + + Existence of last workfile is checked. If workfile does not exists tries + to copy templated workfile from predefined path. + """ + app_groups = ["tvpaint"] + + def execute(self): + # Pop tvpaint executable + tvpaint_executable = self.launch_context.launch_args.pop(0) + + # Pop rest of launch arguments - There should not be other arguments! + remainders = [] + while self.launch_context.launch_args: + remainders.append(self.launch_context.launch_args.pop(0)) + + new_launch_args = [ + self.main_executable(), + self.launch_script_path(), + tvpaint_executable + ] + + # Add workfile to launch arguments + workfile_path = self.workfile_path() + if workfile_path: + new_launch_args.append(workfile_path) + + # How to create new command line + # if platform.system().lower() == "windows": + # new_launch_args = [ + # "cmd.exe", + # "/c", + # "Call cmd.exe /k", + # *new_launch_args + # ] + + # Append as whole list as these areguments should not be separated + self.launch_context.launch_args.append(new_launch_args) + + if remainders: + self.log.warning(( + "There are unexpected launch arguments in TVPaint launch. {}" + ).format(str(remainders))) + self.launch_context.launch_args.extend(remainders) + + def main_executable(self): + """Should lead to python executable.""" + # TODO change in Pype 3 + return os.path.normpath(os.environ["PYPE_PYTHON_EXE"]) + + def launch_script_path(self): + avalon_dir = os.path.dirname(os.path.abspath(avalon.__file__)) + script_path = os.path.join( + avalon_dir, + "tvpaint", + "launch_script.py" + ) + return script_path + + def workfile_path(self): + workfile_path = self.data["last_workfile_path"] + + # copy workfile from template if doesnt exist any on path + if not os.path.exists(workfile_path): + # TODO add ability to set different template workfile path via + # settings + pype_dir = os.path.dirname(os.path.abspath(tvpaint.__file__)) + template_path = os.path.join(pype_dir, "template.tvpp") + + if not os.path.exists(template_path): + self.log.warning( + "Couldn't find workfile template file in {}".format( + template_path + ) + ) + return + + self.log.info( + f"Creating workfile from template: \"{template_path}\"" + ) + + # Copy template workfile to new destinantion + shutil.copy2( + os.path.normpath(template_path), + os.path.normpath(workfile_path) + ) + + self.log.info(f"Workfile to open: \"{workfile_path}\"") + + return workfile_path diff --git a/pype/hooks/unreal/pre_workfile_preparation.py b/pype/hooks/unreal/pre_workfile_preparation.py new file mode 100644 index 0000000000..f0e09669dc --- /dev/null +++ b/pype/hooks/unreal/pre_workfile_preparation.py @@ -0,0 +1,95 @@ +import os + +from pype.lib import ( + PreLaunchHook, + ApplicationLaunchFailed +) +from pype.hosts.unreal import lib as unreal_lib + + +class UnrealPrelaunchHook(PreLaunchHook): + """ + This hook will check if current workfile path has Unreal + project inside. IF not, it initialize it and finally it pass + path to the project by environment variable to Unreal launcher + shell script. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.signature = "( {} )".format(self.__class__.__name__) + + def execute(self): + asset_name = self.data["asset_name"] + task_name = self.data["task_name"] + workdir = self.env["AVALON_WORKDIR"] + engine_version = self.app_name.split("_")[-1] + unreal_project_name = f"{asset_name}_{task_name}" + + # Unreal is sensitive about project names longer then 20 chars + if len(unreal_project_name) > 20: + self.log.warning(( + f"Project name exceed 20 characters ({unreal_project_name})!" + )) + + # Unreal doesn't accept non alphabet characters at the start + # of the project name. This is because project name is then used + # in various places inside c++ code and there variable names cannot + # start with non-alpha. We append 'P' before project name to solve it. + # 😱 + if not unreal_project_name[:1].isalpha(): + self.log.warning(( + "Project name doesn't start with alphabet " + f"character ({unreal_project_name}). Appending 'P'" + )) + unreal_project_name = f"P{unreal_project_name}" + + project_path = os.path.join(workdir, unreal_project_name) + + self.log.info(( + f"{self.signature} requested UE4 version: " + f"[ {engine_version} ]" + )) + + detected = unreal_lib.get_engine_versions() + detected_str = ', '.join(detected.keys()) or 'none' + self.log.info(( + f"{self.signature} detected UE4 versions: " + f"[ {detected_str} ]" + )) + + engine_version = ".".join(engine_version.split(".")[:2]) + if engine_version not in detected.keys(): + raise ApplicationLaunchFailed(( + f"{self.signature} requested version not " + f"detected [ {engine_version} ]" + )) + + os.makedirs(project_path, exist_ok=True) + + project_file = os.path.join( + project_path, + f"{unreal_project_name}.uproject" + ) + if not os.path.isfile(project_file): + engine_path = detected[engine_version] + self.log.info(( + f"{self.signature} creating unreal " + f"project [ {unreal_project_name} ]" + )) + # Set "AVALON_UNREAL_PLUGIN" to current process environment for + # execution of `create_unreal_project` + env_key = "AVALON_UNREAL_PLUGIN" + if self.env.get(env_key): + os.environ[env_key] = self.env[env_key] + + unreal_lib.create_unreal_project( + unreal_project_name, + engine_version, + project_path, + engine_path=engine_path + ) + + # Append project file to launch arguments + self.launch_context.launch_args.append(f"\"{project_file}\"") diff --git a/pype/hosts/harmony/__init__.py b/pype/hosts/harmony/__init__.py index 7ea261292e..b91c0ad4b1 100644 --- a/pype/hosts/harmony/__init__.py +++ b/pype/hosts/harmony/__init__.py @@ -9,7 +9,7 @@ import avalon.tools.sceneinventory import pyblish.api from pype import lib -from pype.api import config +from pype.api import get_current_project_settings def set_scene_settings(settings): @@ -50,10 +50,18 @@ def get_asset_settings(): } try: - skip_resolution_check = \ - config.get_presets()["harmony"]["general"]["skip_resolution_check"] - skip_timelines_check = \ - config.get_presets()["harmony"]["general"]["skip_timelines_check"] + skip_resolution_check = ( + get_current_project_settings() + ["harmony"] + ["general"] + ["skip_resolution_check"] + ) + skip_timelines_check = ( + get_current_project_settings() + ["harmony"] + ["general"] + ["skip_timelines_check"] + ) except KeyError: skip_resolution_check = [] skip_timelines_check = [] diff --git a/pype/hosts/maya/expected_files.py b/pype/hosts/maya/expected_files.py index 8d225bc13d..a2ddec1640 100644 --- a/pype/hosts/maya/expected_files.py +++ b/pype/hosts/maya/expected_files.py @@ -266,8 +266,8 @@ class AExpectedFiles: def _generate_single_file_sequence(self, layer_data): expected_files = [] - file_prefix = layer_data["filePrefix"] for cam in layer_data["cameras"]: + file_prefix = layer_data["filePrefix"] mappings = ( (R_SUBSTITUTE_SCENE_TOKEN, layer_data["sceneName"]), (R_SUBSTITUTE_LAYER_TOKEN, layer_data["layerName"]), @@ -299,9 +299,9 @@ class AExpectedFiles: def _generate_aov_file_sequences(self, layer_data): expected_files = [] aov_file_list = {} - file_prefix = layer_data["filePrefix"] for aov in layer_data["enabledAOVs"]: for cam in layer_data["cameras"]: + file_prefix = layer_data["filePrefix"] mappings = ( (R_SUBSTITUTE_SCENE_TOKEN, layer_data["sceneName"]), @@ -418,13 +418,12 @@ class AExpectedFiles: if connections: for connection in connections: if connection: - node_name = connection.split(".")[0] - if cmds.nodeType(node_name) == "renderLayer": - attr_name = "%s.value" % ".".join( - connection.split(".")[:-1] - ) - if node_name == layer: - yield cmds.getAttr(attr_name) + # node_name = connection.split(".")[0] + + attr_name = "%s.value" % ".".join( + connection.split(".")[:-1] + ) + yield cmds.getAttr(attr_name) def get_render_attribute(self, attr): """Get attribute from render options. @@ -572,11 +571,17 @@ class ExpectedFilesVray(AExpectedFiles): expected_files = super(ExpectedFilesVray, self).get_files() layer_data = self._get_layer_data() + # remove 'beauty' from filenames as vray doesn't output it + update = {} if layer_data.get("enabledAOVs"): - expected_files[0][u"beauty"] = self._generate_single_file_sequence( - layer_data - ) # noqa: E501 + for aov, seq in expected_files[0].items(): + if aov.startswith("beauty"): + new_list = [] + for f in seq: + new_list.append(f.replace("_beauty", "")) + update[aov] = new_list + expected_files[0].update(update) return expected_files def get_aovs(self): @@ -630,28 +635,49 @@ class ExpectedFilesVray(AExpectedFiles): # todo: find how vray set format for AOVs enabled_aovs.append( (self._get_vray_aov_name(aov), default_ext)) + enabled_aovs.append( + (u"beauty", default_ext) + ) return enabled_aovs def _get_vray_aov_name(self, node): + """Get AOVs name from Vray. - # Get render element pass type - vray_node_attr = next( - attr - for attr in cmds.listAttr(node) - if attr.startswith("vray_name") - ) - pass_type = vray_node_attr.rsplit("_", 1)[-1] + Args: + node (str): aov node name. - # Support V-Ray extratex explicit name (if set by user) - if pass_type == "extratex": - explicit_attr = "{}.vray_explicit_name_extratex".format(node) - explicit_name = cmds.getAttr(explicit_attr) - if explicit_name: - return explicit_name + Returns: + str: aov name. - # Node type is in the attribute name but we need to check if value - # of the attribute as it can be changed - return cmds.getAttr("{}.{}".format(node, vray_node_attr)) + """ + vray_name = None + vray_explicit_name = None + vray_file_name = None + for attr in cmds.listAttr(node): + if attr.startswith("vray_filename"): + vray_file_name = cmds.getAttr("{}.{}".format(node, attr)) + elif attr.startswith("vray_name"): + vray_name = cmds.getAttr("{}.{}".format(node, attr)) + elif attr.startswith("vray_explicit_name"): + vray_explicit_name = cmds.getAttr("{}.{}".format(node, attr)) + + if vray_file_name is not None and vray_file_name != "": + final_name = vray_file_name + elif vray_explicit_name is not None and vray_explicit_name != "": + final_name = vray_explicit_name + elif vray_name is not None and vray_name != "": + final_name = vray_name + else: + continue + # special case for Material Select elements - these are named + # based on the materia they are connected to. + if "vray_mtl_mtlselect" in cmds.listAttr(node): + connections = cmds.listConnections( + "{}.vray_mtl_mtlselect".format(node)) + if connections: + final_name += '_{}'.format(str(connections[0])) + + return final_name class ExpectedFilesRedshift(AExpectedFiles): diff --git a/pype/hosts/nuke/plugin.py b/pype/hosts/nuke/plugin.py index 652c0396a8..5d00b19ec5 100644 --- a/pype/hosts/nuke/plugin.py +++ b/pype/hosts/nuke/plugin.py @@ -1,13 +1,13 @@ import re import avalon.api import avalon.nuke -from pype.api import config +from pype.api import get_current_project_settings class PypeCreator(avalon.nuke.pipeline.Creator): """Pype Nuke Creator class wrapper """ def __init__(self, *args, **kwargs): super(PypeCreator, self).__init__(*args, **kwargs) - self.presets = config.get_presets()['plugins']["nuke"]["create"].get( + self.presets = get_current_project_settings()["nuke"]["create"].get( self.__class__.__name__, {} ) diff --git a/pype/hosts/unreal/lib.py b/pype/hosts/unreal/lib.py index 5534c0cbc8..02b1ae5bf5 100644 --- a/pype/hosts/unreal/lib.py +++ b/pype/hosts/unreal/lib.py @@ -4,7 +4,7 @@ import platform import json from distutils import dir_util import subprocess -from pype.api import config +from pype.api import get_project_settings def get_engine_versions(): @@ -150,7 +150,7 @@ def create_unreal_project(project_name: str, :type dev_mode: bool :returns: None """ - preset = config.get_presets()["unreal"]["project_setup"] + preset = get_project_settings(project_name)["unreal"]["project_setup"] if os.path.isdir(os.environ.get("AVALON_UNREAL_PLUGIN", "")): # copy plugin to correct path under project @@ -246,15 +246,18 @@ def create_unreal_project(project_name: str, with open(project_file, mode="w") as pf: json.dump(data, pf, indent=4) - # ensure we have PySide installed in engine - # TODO: make it work for other platforms 🍎 🐧 - if platform.system().lower() == "windows": - python_path = os.path.join(engine_path, "Engine", "Binaries", - "ThirdParty", "Python", "Win64", - "python.exe") + # UE < 4.26 have Python2 by default, so we need PySide + # but we will not need it in 4.26 and up + if int(ue_version.split(".")[1]) < 26: + # ensure we have PySide installed in engine + # TODO: make it work for other platforms 🍎 🐧 + if platform.system().lower() == "windows": + python_path = os.path.join(engine_path, "Engine", "Binaries", + "ThirdParty", "Python", "Win64", + "python.exe") - subprocess.run([python_path, "-m", - "pip", "install", "pyside"]) + subprocess.run([python_path, "-m", + "pip", "install", "pyside"]) if dev_mode or preset["dev_mode"]: _prepare_cpp_project(project_file, engine_path) diff --git a/pype/lib/__init__.py b/pype/lib/__init__.py index c8e10f5b8b..9eb6608df7 100644 --- a/pype/lib/__init__.py +++ b/pype/lib/__init__.py @@ -25,6 +25,12 @@ from .env_tools import ( get_paths_from_environ ) +from .python_module_tools import ( + modules_from_path, + recursive_bases_from_class, + classes_from_module +) + from .avalon_context import ( is_latest, any_outdated, @@ -42,8 +48,8 @@ from .applications import ( ApplictionExecutableNotFound, ApplicationNotFound, ApplicationManager, - launch_application, - ApplicationAction, + PreLaunchHook, + PostLaunchHook, _subprocess ) @@ -65,12 +71,13 @@ from .ffmpeg_utils import ( terminal = Terminal __all__ = [ - "get_avalon_database", - "set_io_database", - "env_value_to_bool", "get_paths_from_environ", + "modules_from_path", + "recursive_bases_from_class", + "classes_from_module", + "is_latest", "any_outdated", "get_asset", @@ -86,8 +93,8 @@ __all__ = [ "ApplictionExecutableNotFound", "ApplicationNotFound", "ApplicationManager", - "launch_application", - "ApplicationAction", + "PreLaunchHook", + "PostLaunchHook", "filter_pyblish_plugins", diff --git a/pype/lib/abstract_submit_deadline.py b/pype/lib/abstract_submit_deadline.py index 09916523a4..170e4908b7 100644 --- a/pype/lib/abstract_submit_deadline.py +++ b/pype/lib/abstract_submit_deadline.py @@ -520,6 +520,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): f.replace(orig_scene, new_scene) ) new_exp[aov] = replaced_files + # [] might be too much here, TODO self._instance.data["expectedFiles"] = [new_exp] else: new_exp = [] @@ -527,7 +528,8 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): new_exp.append( f.replace(orig_scene, new_scene) ) - self._instance.data["expectedFiles"] = [new_exp] + self._instance.data["expectedFiles"] = new_exp + self.log.info("Scene name was switched {} -> {}".format( orig_scene, new_scene )) diff --git a/pype/lib/applications.py b/pype/lib/applications.py index fbbeb2dbf7..7d8635b30a 100644 --- a/pype/lib/applications.py +++ b/pype/lib/applications.py @@ -1,30 +1,20 @@ import os -import sys -import re -import getpass -import json import copy import platform -import logging +import inspect import subprocess +import distutils.spawn +from abc import ABCMeta, abstractmethod -import acre +import six -import avalon.lib -import avalon.api +from pype.settings import get_system_settings, get_environments +from ..api import Logger -from ..api import ( - Anatomy, - Logger, - config, - system_settings, - environments +from .python_module_tools import ( + modules_from_path, + classes_from_module ) -from .hooks import execute_hook -from .deprecated import get_avalon_database -from .env_tools import env_value_to_bool - -log = logging.getLogger(__name__) class ApplicationNotFound(Exception): @@ -53,8 +43,8 @@ class ApplictionExecutableNotFound(Exception): " are not available on this machine." ) details = "Defined paths:" - for executable_path in application.executables: - details += "\n- " + executable_path + for executable in application.executables: + details += "\n- " + executable.executable_path self.msg = msg.format(application.full_label, application.app_name) self.details = details @@ -75,398 +65,6 @@ class ApplicationLaunchFailed(Exception): pass -def compile_list_of_regexes(in_list): - """Convert strings in entered list to compiled regex objects.""" - regexes = list() - if not in_list: - return regexes - - for item in in_list: - if item: - try: - regexes.append(re.compile(item)) - except TypeError: - print(( - "Invalid type \"{}\" value \"{}\"." - " Expected string based object. Skipping." - ).format(str(type(item)), str(item))) - return regexes - - -def launch_application(project_name, asset_name, task_name, app_name): - """Launch host application with filling required environments. - - TODO(iLLiCiT): This should be split into more parts. - """ - # `get_avalon_database` is in Pype 3 replaced with using `AvalonMongoDB` - import acre - import avalon.lib - - database = get_avalon_database() - project_document = database[project_name].find_one({"type": "project"}) - asset_document = database[project_name].find_one({ - "type": "asset", - "name": asset_name - }) - - asset_doc_parents = asset_document["data"].get("parents") - hierarchy = "/".join(asset_doc_parents) - - app_def = avalon.lib.get_application(app_name) - app_label = app_def.get("ftrack_label", app_def.get("label", app_name)) - - host_name = app_def["application_dir"] - # Workfile data collection may be special function? - data = { - "project": { - "name": project_document["name"], - "code": project_document["data"].get("code") - }, - "task": task_name, - "asset": asset_name, - "app": host_name, - "hierarchy": hierarchy - } - - try: - anatomy = Anatomy(project_name) - anatomy_filled = anatomy.format(data) - workdir = os.path.normpath(anatomy_filled["work"]["folder"]) - - except Exception as exc: - raise ApplicationLaunchFailed( - "Error in anatomy.format: {}".format(str(exc)) - ) - - try: - os.makedirs(workdir) - except FileExistsError: - pass - - last_workfile_path = None - extensions = avalon.api.HOST_WORKFILE_EXTENSIONS.get(host_name) - if extensions: - # Find last workfile - file_template = anatomy.templates["work"]["file"] - data.update({ - "version": 1, - "user": os.environ.get("PYPE_USERNAME") or getpass.getuser(), - "ext": extensions[0] - }) - - last_workfile_path = avalon.api.last_workfile( - workdir, file_template, data, extensions, True - ) - - # set environments for Avalon - prep_env = copy.deepcopy(os.environ) - prep_env.update({ - "AVALON_PROJECT": project_name, - "AVALON_ASSET": asset_name, - "AVALON_TASK": task_name, - "AVALON_APP": host_name, - "AVALON_APP_NAME": app_name, - "AVALON_HIERARCHY": hierarchy, - "AVALON_WORKDIR": workdir - }) - - start_last_workfile = avalon.api.should_start_last_workfile( - project_name, host_name, task_name - ) - # Store boolean as "0"(False) or "1"(True) - prep_env["AVALON_OPEN_LAST_WORKFILE"] = ( - str(int(bool(start_last_workfile))) - ) - - if ( - start_last_workfile - and last_workfile_path - and os.path.exists(last_workfile_path) - ): - prep_env["AVALON_LAST_WORKFILE"] = last_workfile_path - - prep_env.update(anatomy.roots_obj.root_environments()) - - # collect all the 'environment' attributes from parents - tools_attr = [prep_env["AVALON_APP"], prep_env["AVALON_APP_NAME"]] - tools_env = asset_document["data"].get("tools_env") or [] - tools_attr.extend(tools_env) - - tools_env = acre.get_tools(tools_attr) - env = acre.compute(tools_env) - env = acre.merge(env, current_env=dict(prep_env)) - - # Get path to execute - st_temp_path = os.environ["PYPE_CONFIG"] - os_plat = platform.system().lower() - - # Path to folder with launchers - path = os.path.join(st_temp_path, "launchers", os_plat) - - # Full path to executable launcher - execfile = None - - launch_hook = app_def.get("launch_hook") - if launch_hook: - log.info("launching hook: {}".format(launch_hook)) - ret_val = execute_hook(launch_hook, env=env) - if not ret_val: - raise ApplicationLaunchFailed( - "Hook didn't finish successfully {}".format(app_label) - ) - - if sys.platform == "win32": - for ext in os.environ["PATHEXT"].split(os.pathsep): - fpath = os.path.join(path.strip('"'), app_def["executable"] + ext) - if os.path.isfile(fpath) and os.access(fpath, os.X_OK): - execfile = fpath - break - - # Run SW if was found executable - if execfile is None: - raise ApplicationLaunchFailed( - "We didn't find launcher for {}".format(app_label) - ) - - popen = avalon.lib.launch( - executable=execfile, args=[], environment=env - ) - - elif ( - sys.platform.startswith("linux") - or sys.platform.startswith("darwin") - ): - execfile = os.path.join(path.strip('"'), app_def["executable"]) - # Run SW if was found executable - if execfile is None: - raise ApplicationLaunchFailed( - "We didn't find launcher for {}".format(app_label) - ) - - if not os.path.isfile(execfile): - raise ApplicationLaunchFailed( - "Launcher doesn't exist - {}".format(execfile) - ) - - try: - fp = open(execfile) - except PermissionError as perm_exc: - raise ApplicationLaunchFailed( - "Access denied on launcher {} - {}".format(execfile, perm_exc) - ) - - fp.close() - # check executable permission - if not os.access(execfile, os.X_OK): - raise ApplicationLaunchFailed( - "No executable permission - {}".format(execfile) - ) - - popen = avalon.lib.launch( # noqa: F841 - "/usr/bin/env", args=["bash", execfile], environment=env - ) - return popen - - -class ApplicationAction: - """Default application launcher - - This is a convenience application Action that when "config" refers to a - parsed application `.toml` this can launch the application. - - """ - _log = None - config = None - group = None - variant = None - required_session_keys = ( - "AVALON_PROJECT", - "AVALON_ASSET", - "AVALON_TASK" - ) - - @property - def log(self): - if self._log is None: - self._log = logging.getLogger(self.__class__.__name__) - return self._log - - def is_compatible(self, session): - for key in self.required_session_keys: - if key not in session: - return False - return True - - def process(self, session, **kwargs): - """Process the full Application action""" - - project_name = session["AVALON_PROJECT"] - asset_name = session["AVALON_ASSET"] - task_name = session["AVALON_TASK"] - launch_application( - project_name, asset_name, task_name, self.name - ) - - self._ftrack_after_launch_procedure( - project_name, asset_name, task_name - ) - - def _ftrack_after_launch_procedure( - self, project_name, asset_name, task_name - ): - # TODO move to launch hook - required_keys = ("FTRACK_SERVER", "FTRACK_API_USER", "FTRACK_API_KEY") - for key in required_keys: - if not os.environ.get(key): - self.log.debug(( - "Missing required environment \"{}\"" - " for Ftrack after launch procedure." - ).format(key)) - return - - try: - import ftrack_api - session = ftrack_api.Session(auto_connect_event_hub=True) - self.log.debug("Ftrack session created") - except Exception: - self.log.warning("Couldn't create Ftrack session") - return - - try: - entity = self._find_ftrack_task_entity( - session, project_name, asset_name, task_name - ) - self._ftrack_status_change(session, entity, project_name) - self._start_timer(session, entity, ftrack_api) - except Exception: - self.log.warning( - "Couldn't finish Ftrack procedure.", exc_info=True - ) - return - - finally: - session.close() - - def _find_ftrack_task_entity( - self, session, project_name, asset_name, task_name - ): - project_entity = session.query( - "Project where full_name is \"{}\"".format(project_name) - ).first() - if not project_entity: - self.log.warning( - "Couldn't find project \"{}\" in Ftrack.".format(project_name) - ) - return - - potential_task_entities = session.query(( - "TypedContext where parent.name is \"{}\" and project_id is \"{}\"" - ).format(asset_name, project_entity["id"])).all() - filtered_entities = [] - for _entity in potential_task_entities: - if ( - _entity.entity_type.lower() == "task" - and _entity["name"] == task_name - ): - filtered_entities.append(_entity) - - if not filtered_entities: - self.log.warning(( - "Couldn't find task \"{}\" under parent \"{}\" in Ftrack." - ).format(task_name, asset_name)) - return - - if len(filtered_entities) > 1: - self.log.warning(( - "Found more than one task \"{}\"" - " under parent \"{}\" in Ftrack." - ).format(task_name, asset_name)) - return - - return filtered_entities[0] - - def _ftrack_status_change(self, session, entity, project_name): - presets = config.get_presets(project_name)["ftrack"]["ftrack_config"] - statuses = presets.get("status_update") - if not statuses: - return - - actual_status = entity["status"]["name"].lower() - already_tested = set() - ent_path = "/".join( - [ent["name"] for ent in entity["link"]] - ) - while True: - next_status_name = None - for key, value in statuses.items(): - if key in already_tested: - continue - if actual_status in value or "_any_" in value: - if key != "_ignore_": - next_status_name = key - already_tested.add(key) - break - already_tested.add(key) - - if next_status_name is None: - break - - try: - query = "Status where name is \"{}\"".format( - next_status_name - ) - status = session.query(query).one() - - entity["status"] = status - session.commit() - self.log.debug("Changing status to \"{}\" <{}>".format( - next_status_name, ent_path - )) - break - - except Exception: - session.rollback() - msg = ( - "Status \"{}\" in presets wasn't found" - " on Ftrack entity type \"{}\"" - ).format(next_status_name, entity.entity_type) - self.log.warning(msg) - - def _start_timer(self, session, entity, _ftrack_api): - self.log.debug("Triggering timer start.") - - user_entity = session.query("User where username is \"{}\"".format( - os.environ["FTRACK_API_USER"] - )).first() - if not user_entity: - self.log.warning( - "Couldn't find user with username \"{}\" in Ftrack".format( - os.environ["FTRACK_API_USER"] - ) - ) - return - - source = { - "user": { - "id": user_entity["id"], - "username": user_entity["username"] - } - } - event_data = { - "actionIdentifier": "start.timer", - "selection": [{"entityId": entity["id"], "entityType": "task"}] - } - session.event_hub.publish( - _ftrack_api.event.base.Event( - topic="ftrack.action.launch", - data=event_data, - source=source - ), - on_error="ignore" - ) - self.log.debug("Timer start triggered successfully.") - - # Special naming case for subprocess since its a built-in method. def _subprocess(*args, **kwargs): """Convenience method for getting output errors for subprocess. @@ -543,15 +141,15 @@ class ApplicationManager: def refresh(self): """Refresh applications from settings.""" - settings = system_settings() + settings = get_system_settings() - hosts_definitions = settings["global"]["applications"] - for host_name, variant_definitions in hosts_definitions.items(): + hosts_definitions = settings["applications"] + for app_group, variant_definitions in hosts_definitions.items(): enabled = variant_definitions["enabled"] - label = variant_definitions.get("label") or host_name + label = variant_definitions.get("label") or app_group variants = variant_definitions.get("variants") or {} icon = variant_definitions.get("icon") - is_host = variant_definitions.get("is_host", False) + group_host_name = variant_definitions.get("host_name") or None for app_name, app_data in variants.items(): if app_name in self.applications: raise AssertionError(( @@ -569,14 +167,15 @@ class ApplicationManager: if not app_data.get("icon"): app_data["icon"] = icon - is_host = app_data.get("is_host", is_host) - app_data["is_host"] = is_host + host_name = app_data.get("host_name") or group_host_name + + app_data["is_host"] = host_name is not None self.applications[app_name] = Application( - host_name, app_name, app_data, self + app_group, app_name, host_name, app_data, self ) - tools_definitions = settings["global"]["tools"] + tools_definitions = settings["tools"] for tool_group_name, tool_group_data in tools_definitions.items(): enabled = tool_group_data.get("enabled", True) tool_variants = tool_group_data.get("variants") or {} @@ -646,25 +245,62 @@ class ApplicationTool: return self.enabled +class ApplicationExecutable: + def __init__(self, executable): + default_launch_args = [] + if isinstance(executable, str): + executable_path = executable + + elif isinstance(executable, list): + executable_path = None + for arg in executable: + if arg: + if executable_path is None: + executable_path = arg + else: + default_launch_args.append(arg) + + self.executable_path = executable_path + self.default_launch_args = default_launch_args + + def __iter__(self): + yield distutils.spawn.find_executable(self.executable_path) + for arg in self.default_launch_args: + yield arg + + def __str__(self): + return self.executable_path + + def as_args(self): + return list(self) + + def exists(self): + if not self.executable_path: + return False + return bool(distutils.spawn.find_executable(self.executable_path)) + + class Application: """Hold information about application. Object by itself does nothing special. Args: - host_name (str): Host name or rather name of host implementation. + app_group (str): App group name. e.g. "maya", "nuke", "photoshop", etc. app_name (str): Specific version (or variant) of host. e.g. "maya2020", "nuke11.3", etc. + host_name (str): Name of host implementation. app_data (dict): Data for the version containing information about executables, label, variant label, icon or if is enabled. Only required key is `executables`. manager (ApplicationManager): Application manager that created object. """ - def __init__(self, host_name, app_name, app_data, manager): - self.host_name = host_name + def __init__(self, app_group, app_name, host_name, app_data, manager): + self.app_group = app_group self.app_name = app_name + self.host_name = host_name self.app_data = app_data self.manager = manager @@ -674,12 +310,17 @@ class Application: self.enabled = app_data.get("enabled", True) self.is_host = app_data.get("is_host", False) - executables = app_data["executables"] - if isinstance(executables, dict): - executables = executables.get(platform.system().lower()) or [] + _executables = app_data["executables"] + if not _executables: + _executables = [] + + elif isinstance(_executables, dict): + _executables = _executables.get(platform.system().lower()) or [] + + executables = [] + for executable in _executables: + executables.append(ApplicationExecutable(executable)) - if not isinstance(executables, list): - executables = [executables] self.executables = executables @property @@ -699,9 +340,9 @@ class Application: Returns (str): Path to executable from `executables` or None if any exists. """ - for executable_path in self.executables: - if os.path.exists(executable_path): - return executable_path + for executable in self.executables: + if executable.exists(): + return executable return None def launch(self, *args, **kwargs): @@ -719,6 +360,128 @@ class Application: return self.manager.launch(self.app_name, *args, **kwargs) +@six.add_metaclass(ABCMeta) +class LaunchHook: + """Abstract base class of launch hook.""" + # Order of prelaunch hook, will be executed as last if set to None. + order = None + # List of host implementations, skipped if empty. + hosts = [] + # List of application groups + app_groups = [] + # List of specific application names + app_names = [] + # List of platform availability, skipped if empty. + platforms = [] + + def __init__(self, launch_context): + """Constructor of launch hook. + + Always should be called + """ + self.log = Logger().get_logger(self.__class__.__name__) + + self.launch_context = launch_context + + is_valid = self.class_validation(launch_context) + if is_valid: + is_valid = self.validate() + + self.is_valid = is_valid + + @classmethod + def class_validation(cls, launch_context): + """Validation of class attributes by launch context. + + Args: + launch_context (ApplicationLaunchContext): Context of launching + application. + + Returns: + bool: Is launch hook valid for the context by class attributes. + """ + if cls.platforms: + low_platforms = tuple( + _platform.lower() + for _platform in cls.platforms + ) + if platform.system().lower() not in low_platforms: + return False + + if cls.hosts: + if launch_context.host_name not in cls.hosts: + return False + + if cls.app_groups: + if launch_context.app_group not in cls.app_groups: + return False + + if cls.app_names: + if launch_context.app_name not in cls.app_names: + return False + + return True + + @property + def data(self): + return self.launch_context.data + + @property + def application(self): + return getattr(self.launch_context, "application", None) + + @property + def manager(self): + return getattr(self.application, "manager", None) + + @property + def host_name(self): + return getattr(self.application, "host_name", None) + + @property + def app_group(self): + return getattr(self.application, "app_group", None) + + @property + def app_name(self): + return getattr(self.application, "app_name", None) + + def validate(self): + """Optional validation of launch hook on initialization. + + Returns: + bool: Hook is valid (True) or invalid (False). + """ + # QUESTION Not sure if this method has any usable potential. + # - maybe result can be based on settings + return True + + @abstractmethod + def execute(self, *args, **kwargs): + """Abstract execute method where logic of hook is.""" + pass + + +class PreLaunchHook(LaunchHook): + """Abstract class of prelaunch hook. + + This launch hook will be processed before application is launched. + + If any exception will happen during processing the application won't be + launched. + """ + + +class PostLaunchHook(LaunchHook): + """Abstract class of postlaunch hook. + + This launch hook will be processed after application is launched. + + Nothing will happen if any exception will happen during processing. And + processing of other postlaunch hooks won't stop either. + """ + + class ApplicationLaunchContext: """Context of launching application. @@ -739,7 +502,7 @@ class ApplicationLaunchContext: Args: application (Application): Application definition. - executable (str): Path to executable. + executable (ApplicationExecutable): Object with path to executable. **data (dict): Any additional data. Data may be used during preparation to store objects usable in multiple places. """ @@ -756,25 +519,25 @@ class ApplicationLaunchContext: self.data = dict(data) + # Load settings if were not passed in data + settings_env = self.data.get("settings_env") + if settings_env is None: + settings_env = get_environments() + self.data["settings_env"] = settings_env + + # subprocess.Popen launch arguments (first argument in constructor) + self.launch_args = executable.as_args() + # Handle launch environemtns passed_env = self.data.pop("env", None) if passed_env is None: env = os.environ else: env = passed_env - self.env = copy.deepcopy(env) - # Load settings if were not passed in data - settings_env = self.data.get("settings_env") - if settings_env is None: - settings_env = environments() - self.data["settings_env"] = settings_env - - # subprocess.Popen launch arguments (first argument in constructor) - self.launch_args = [executable] # subprocess.Popen keyword arguments self.kwargs = { - "env": self.env + "env": copy.deepcopy(env) } if platform.system().lower() == "windows": @@ -785,12 +548,134 @@ class ApplicationLaunchContext: ) self.kwargs["creationflags"] = flags + self.prelaunch_hooks = None + self.postlaunch_hooks = None + self.process = None - # TODO move these to pre-paunch hook - self.prepare_global_data() - self.prepare_host_environments() - self.prepare_context_environments() + @property + def env(self): + if ( + "env" not in self.kwargs + or self.kwargs["env"] is None + ): + self.kwargs["env"] = {} + return self.kwargs["env"] + + @env.setter + def env(self, value): + if not isinstance(value, dict): + raise ValueError( + "'env' attribute expect 'dict' object. Got: {}".format( + str(type(value)) + ) + ) + self.kwargs["env"] = value + + def paths_to_launch_hooks(self): + """Directory paths where to look for launch hooks.""" + # This method has potential to be part of application manager (maybe). + + # TODO find better way how to define dir path to default launch hooks + import pype + pype_dir = os.path.dirname(os.path.abspath(pype.__file__)) + hooks_dir = os.path.join(pype_dir, "hooks") + + # TODO load additional studio paths from settings + # TODO add paths based on used modules (like `ftrack`) + paths = [] + subfolder_names = ["global", self.host_name, self.app_name] + for subfolder_name in subfolder_names: + path = os.path.join(hooks_dir, subfolder_name) + if os.path.exists(path) and os.path.isdir(path): + paths.append(path) + return paths + + def discover_launch_hooks(self, force=False): + """Load and prepare launch hooks.""" + if ( + self.prelaunch_hooks is not None + or self.postlaunch_hooks is not None + ): + if not force: + self.log.info("Launch hooks were already discovered.") + return + + self.prelaunch_hooks.clear() + self.postlaunch_hooks.clear() + + self.log.debug("Discovery of launch hooks started.") + + paths = self.paths_to_launch_hooks() + self.log.debug("Paths where will look for launch hooks:{}".format( + "\n- ".join(paths) + )) + + all_classes = { + "pre": [], + "post": [] + } + for path in paths: + if not os.path.exists(path): + self.log.info( + "Path to launch hooks does not exists: \"{}\"".format(path) + ) + continue + + modules = modules_from_path(path) + for _module in modules: + all_classes["pre"].extend( + classes_from_module(PreLaunchHook, _module) + ) + all_classes["post"].extend( + classes_from_module(PostLaunchHook, _module) + ) + + for launch_type, classes in all_classes.items(): + hooks_with_order = [] + hooks_without_order = [] + for klass in classes: + try: + hook = klass(self) + if not hook.is_valid: + self.log.debug( + "Hook is not valid for curent launch context." + ) + continue + + if inspect.isabstract(hook): + self.log.debug("Skipped abstract hook: {}".format( + str(hook) + )) + continue + + # Separate hooks by pre/post class + if hook.order is None: + hooks_without_order.append(hook) + else: + hooks_with_order.append(hook) + + except Exception: + self.log.warning( + "Initialization of hook failed. {}".format(str(klass)), + exc_info=True + ) + + # Sort hooks with order by order + ordered_hooks = list(sorted( + hooks_with_order, key=lambda obj: obj.order + )) + # Extend ordered hooks with hooks without defined order + ordered_hooks.extend(hooks_without_order) + + if launch_type == "pre": + self.prelaunch_hooks = ordered_hooks + else: + self.postlaunch_hooks = ordered_hooks + + self.log.debug("Found {} prelaunch and {} postlaunch hooks.".format( + len(self.prelaunch_hooks), len(self.postlaunch_hooks) + )) @property def app_name(self): @@ -800,6 +685,10 @@ class ApplicationLaunchContext: def host_name(self): return self.application.host_name + @property + def app_group(self): + return self.application.app_group + @property def manager(self): return self.application.manager @@ -816,20 +705,46 @@ class ApplicationLaunchContext: self.log.warning("Application was already launched.") return + # Discover launch hooks + self.discover_launch_hooks() + + # Execute prelaunch hooks + for prelaunch_hook in self.prelaunch_hooks: + self.log.debug("Executing prelaunch hook: {}".format( + str(prelaunch_hook) + )) + prelaunch_hook.execute() + + self.log.debug("All prelaunch hook executed. Starting new process.") + + # Prepare subprocess args args = self.clear_launch_args(self.launch_args) self.log.debug( - "Launching \"{}\" with args: {}".format(self.app_name, args) + "Launching \"{}\" with args ({}): {}".format( + self.app_name, len(args), args + ) ) + # Run process self.process = subprocess.Popen(args, **self.kwargs) - # TODO do this with after-launch hooks - try: - self.after_launch_procedures() - except Exception: - self.log.warning( - "After launch procedures were not successful.", - exc_info=True - ) + # Process post launch hooks + for postlaunch_hook in self.postlaunch_hooks: + self.log.debug("Executing postlaunch hook: {}".format( + str(prelaunch_hook) + )) + + # TODO how to handle errors? + # - store to variable to let them accesible? + try: + postlaunch_hook.execute() + + except Exception: + self.log.warning( + "After launch procedures were not successful.", + exc_info=True + ) + + self.log.debug("Launch of {} finished.".format(self.app_name)) return self.process @@ -861,481 +776,9 @@ class ApplicationLaunchContext: for _arg in arg: new_args.append(_arg) else: - new_args.append(args) + new_args.append(arg) args = new_args if all_cleared: break return args - - def prepare_global_data(self): - """Prepare global objects to `data` that will be used for sure.""" - # Mongo documents - project_name = self.data.get("project_name") - if not project_name: - self.log.info( - "Skipping global data preparation." - " Key `project_name` was not found in launch context." - ) - return - - self.log.debug("Project name is set to \"{}\"".format(project_name)) - # Anatomy - self.data["anatomy"] = Anatomy(project_name) - - # Mongo connection - dbcon = avalon.api.AvalonMongoDB() - dbcon.Session["AVALON_PROJECT"] = project_name - dbcon.install() - - self.data["dbcon"] = dbcon - - # Project document - project_doc = dbcon.find_one({"type": "project"}) - self.data["project_doc"] = project_doc - - asset_name = self.data.get("asset_name") - if not asset_name: - self.log.warning( - "Asset name was not set. Skipping asset document query." - ) - return - - asset_doc = dbcon.find_one({ - "type": "asset", - "name": asset_name - }) - self.data["asset_doc"] = asset_doc - - def _merge_env(self, env, current_env): - """Modified function(merge) from acre module.""" - result = current_env.copy() - for key, value in env.items(): - # Keep missing keys by not filling `missing` kwarg - value = acre.lib.partial_format(value, data=current_env) - result[key] = value - return result - - def prepare_host_environments(self): - """Modify launch environments based on launched app and context.""" - # Keys for getting environments - env_keys = [self.host_name, self.app_name] - - asset_doc = self.data.get("asset_doc") - if asset_doc: - # Add tools environments - for key in asset_doc["data"].get("tools_env") or []: - tool = self.manager.tools.get(key) - if tool: - if tool.group_name not in env_keys: - env_keys.append(tool.group_name) - - if tool.name not in env_keys: - env_keys.append(tool.name) - - self.log.debug( - "Finding environment groups for keys: {}".format(env_keys) - ) - - settings_env = self.data["settings_env"] - env_values = {} - for env_key in env_keys: - _env_values = settings_env.get(env_key) - if not _env_values: - continue - - # Choose right platform - tool_env = acre.parse(_env_values) - # Merge dictionaries - env_values = self._merge_env(tool_env, env_values) - - final_env = self._merge_env(acre.compute(env_values), self.env) - - # Update env - self.env.update(final_env) - - def prepare_context_environments(self): - """Modify launch environemnts with context data for launched host.""" - # Context environments - project_doc = self.data.get("project_doc") - asset_doc = self.data.get("asset_doc") - task_name = self.data.get("task_name") - if ( - not project_doc - or not asset_doc - or not task_name - ): - self.log.info( - "Skipping context environments preparation." - " Launch context does not contain required data." - ) - return - - workdir_data = self._prepare_workdir_data( - project_doc, asset_doc, task_name - ) - self.data["workdir_data"] = workdir_data - - hierarchy = workdir_data["hierarchy"] - anatomy = self.data["anatomy"] - - try: - anatomy_filled = anatomy.format(workdir_data) - workdir = os.path.normpath(anatomy_filled["work"]["folder"]) - if not os.path.exists(workdir): - self.log.debug( - "Creating workdir folder: \"{}\"".format(workdir) - ) - os.makedirs(workdir) - - except Exception as exc: - raise ApplicationLaunchFailed( - "Error in anatomy.format: {}".format(str(exc)) - ) - - context_env = { - "AVALON_PROJECT": project_doc["name"], - "AVALON_ASSET": asset_doc["name"], - "AVALON_TASK": task_name, - "AVALON_APP": self.host_name, - "AVALON_APP_NAME": self.app_name, - "AVALON_HIERARCHY": hierarchy, - "AVALON_WORKDIR": workdir - } - self.log.debug( - "Context environemnts set:\n{}".format( - json.dumps(context_env, indent=4) - ) - ) - self.env.update(context_env) - - self.prepare_last_workfile(workdir) - - def _prepare_workdir_data(self, project_doc, asset_doc, task_name): - hierarchy = "/".join(asset_doc["data"]["parents"]) - - data = { - "project": { - "name": project_doc["name"], - "code": project_doc["data"].get("code") - }, - "task": task_name, - "asset": asset_doc["name"], - "app": self.host_name, - "hierarchy": hierarchy - } - return data - - def prepare_last_workfile(self, workdir): - """last workfile workflow preparation. - - Function check if should care about last workfile workflow and tries - to find the last workfile. Both information are stored to `data` and - environments. - - Last workfile is filled always (with version 1) even if any workfile - exists yet. - - Args: - workdir (str): Path to folder where workfiles should be stored. - """ - _workdir_data = self.data.get("workdir_data") - if not _workdir_data: - self.log.info( - "Skipping last workfile preparation." - " Key `workdir_data` not filled." - ) - return - - workdir_data = copy.deepcopy(_workdir_data) - project_name = self.data["project_name"] - task_name = self.data["task_name"] - start_last_workfile = self.should_start_last_workfile( - project_name, self.host_name, task_name - ) - self.data["start_last_workfile"] = start_last_workfile - - # Store boolean as "0"(False) or "1"(True) - self.env["AVALON_OPEN_LAST_WORKFILE"] = ( - str(int(bool(start_last_workfile))) - ) - - _sub_msg = "" if start_last_workfile else " not" - self.log.debug( - "Last workfile should{} be opened on start.".format(_sub_msg) - ) - - # Last workfile path - last_workfile_path = "" - extensions = avalon.api.HOST_WORKFILE_EXTENSIONS.get(self.host_name) - if extensions: - anatomy = self.data["anatomy"] - # Find last workfile - file_template = anatomy.templates["work"]["file"] - workdir_data.update({ - "version": 1, - "user": os.environ.get("PYPE_USERNAME") or getpass.getuser(), - "ext": extensions[0] - }) - - last_workfile_path = avalon.api.last_workfile( - workdir, file_template, workdir_data, extensions, True - ) - - if os.path.exists(last_workfile_path): - self.log.debug(( - "Workfiles for launch context does not exists" - " yet but path will be set." - )) - self.log.debug( - "Setting last workfile path: {}".format(last_workfile_path) - ) - - self.env["AVALON_LAST_WORKFILE"] = last_workfile_path - self.data["last_workfile_path"] = last_workfile_path - - def should_start_last_workfile(self, project_name, host_name, task_name): - """Define if host should start last version workfile if possible. - - Default output is `False`. Can be overriden with environment variable - `AVALON_OPEN_LAST_WORKFILE`, valid values without case sensitivity are - `"0", "1", "true", "false", "yes", "no"`. - - Args: - project_name (str): Name of project. - host_name (str): Name of host which is launched. In avalon's - application context it's value stored in app definition under - key `"application_dir"`. Is not case sensitive. - task_name (str): Name of task which is used for launching the host. - Task name is not case sensitive. - - Returns: - bool: True if host should start workfile. - - """ - default_output = env_value_to_bool( - "AVALON_OPEN_LAST_WORKFILE", default=False - ) - # TODO convert to settings - try: - startup_presets = ( - config.get_presets(project_name) - .get("tools", {}) - .get("workfiles", {}) - .get("last_workfile_on_startup") - ) - except Exception: - startup_presets = None - self.log.warning("Couldn't load pype's presets", exc_info=True) - - if not startup_presets: - return default_output - - host_name_lowered = host_name.lower() - task_name_lowered = task_name.lower() - - max_points = 2 - matching_points = -1 - matching_item = None - for item in startup_presets: - hosts = item.get("hosts") or tuple() - tasks = item.get("tasks") or tuple() - - hosts_lowered = tuple(_host_name.lower() for _host_name in hosts) - # Skip item if has set hosts and current host is not in - if hosts_lowered and host_name_lowered not in hosts_lowered: - continue - - tasks_lowered = tuple(_task_name.lower() for _task_name in tasks) - # Skip item if has set tasks and current task is not in - if tasks_lowered: - task_match = False - for task_regex in compile_list_of_regexes(tasks_lowered): - if re.match(task_regex, task_name_lowered): - task_match = True - break - - if not task_match: - continue - - points = int(bool(hosts_lowered)) + int(bool(tasks_lowered)) - if points > matching_points: - matching_item = item - matching_points = points - - if matching_points == max_points: - break - - if matching_item is not None: - output = matching_item.get("enabled") - if output is None: - output = default_output - return output - return default_output - - def after_launch_procedures(self): - self._ftrack_after_launch_procedure() - - def _ftrack_after_launch_procedure(self): - # TODO move to launch hook - project_name = self.data.get("project_name") - asset_name = self.data.get("asset_name") - task_name = self.data.get("task_name") - if ( - not project_name - or not asset_name - or not task_name - ): - return - - required_keys = ("FTRACK_SERVER", "FTRACK_API_USER", "FTRACK_API_KEY") - for key in required_keys: - if not os.environ.get(key): - self.log.debug(( - "Missing required environment \"{}\"" - " for Ftrack after launch procedure." - ).format(key)) - return - - try: - import ftrack_api - session = ftrack_api.Session(auto_connect_event_hub=True) - self.log.debug("Ftrack session created") - except Exception: - self.log.warning("Couldn't create Ftrack session") - return - - try: - entity = self._find_ftrack_task_entity( - session, project_name, asset_name, task_name - ) - self._ftrack_status_change(session, entity, project_name) - self._start_timer(session, entity, ftrack_api) - except Exception: - self.log.warning( - "Couldn't finish Ftrack procedure.", exc_info=True - ) - return - - finally: - session.close() - - def _find_ftrack_task_entity( - self, session, project_name, asset_name, task_name - ): - project_entity = session.query( - "Project where full_name is \"{}\"".format(project_name) - ).first() - if not project_entity: - self.log.warning( - "Couldn't find project \"{}\" in Ftrack.".format(project_name) - ) - return - - potential_task_entities = session.query(( - "TypedContext where parent.name is \"{}\" and project_id is \"{}\"" - ).format(asset_name, project_entity["id"])).all() - filtered_entities = [] - for _entity in potential_task_entities: - if ( - _entity.entity_type.lower() == "task" - and _entity["name"] == task_name - ): - filtered_entities.append(_entity) - - if not filtered_entities: - self.log.warning(( - "Couldn't find task \"{}\" under parent \"{}\" in Ftrack." - ).format(task_name, asset_name)) - return - - if len(filtered_entities) > 1: - self.log.warning(( - "Found more than one task \"{}\"" - " under parent \"{}\" in Ftrack." - ).format(task_name, asset_name)) - return - - return filtered_entities[0] - - def _ftrack_status_change(self, session, entity, project_name): - from pype.api import config - presets = config.get_presets(project_name)["ftrack"]["ftrack_config"] - statuses = presets.get("status_update") - if not statuses: - return - - actual_status = entity["status"]["name"].lower() - already_tested = set() - ent_path = "/".join( - [ent["name"] for ent in entity["link"]] - ) - while True: - next_status_name = None - for key, value in statuses.items(): - if key in already_tested: - continue - if actual_status in value or "_any_" in value: - if key != "_ignore_": - next_status_name = key - already_tested.add(key) - break - already_tested.add(key) - - if next_status_name is None: - break - - try: - query = "Status where name is \"{}\"".format( - next_status_name - ) - status = session.query(query).one() - - entity["status"] = status - session.commit() - self.log.debug("Changing status to \"{}\" <{}>".format( - next_status_name, ent_path - )) - break - - except Exception: - session.rollback() - msg = ( - "Status \"{}\" in presets wasn't found" - " on Ftrack entity type \"{}\"" - ).format(next_status_name, entity.entity_type) - self.log.warning(msg) - - def _start_timer(self, session, entity, _ftrack_api): - self.log.debug("Triggering timer start.") - - user_entity = session.query("User where username is \"{}\"".format( - os.environ["FTRACK_API_USER"] - )).first() - if not user_entity: - self.log.warning( - "Couldn't find user with username \"{}\" in Ftrack".format( - os.environ["FTRACK_API_USER"] - ) - ) - return - - source = { - "user": { - "id": user_entity["id"], - "username": user_entity["username"] - } - } - event_data = { - "actionIdentifier": "start.timer", - "selection": [{"entityId": entity["id"], "entityType": "task"}] - } - session.event_hub.publish( - _ftrack_api.event.base.Event( - topic="ftrack.action.launch", - data=event_data, - source=source - ), - on_error="ignore" - ) - self.log.debug("Timer start triggered successfully.") diff --git a/pype/lib/avalon_context.py b/pype/lib/avalon_context.py index 48f388785e..e0dca20789 100644 --- a/pype/lib/avalon_context.py +++ b/pype/lib/avalon_context.py @@ -426,12 +426,12 @@ class BuildWorkfile: (dict): preset per entered task name """ host_name = avalon.api.registered_host().__name__.rsplit(".", 1)[-1] - presets = config.get_presets(io.Session["AVALON_PROJECT"]) + presets = get_project_settings(io.Session["AVALON_PROJECT"]) # Get presets for host build_presets = ( - presets["plugins"] - .get(host_name, {}) + presets.get(host_name, {}) .get("workfile_build") + .get("profiles") ) if not build_presets: return diff --git a/pype/lib/env_tools.py b/pype/lib/env_tools.py index f31426103b..a5176b814d 100644 --- a/pype/lib/env_tools.py +++ b/pype/lib/env_tools.py @@ -20,9 +20,9 @@ def env_value_to_bool(env_key=None, value=None, default=False): if value is not None: value = str(value).lower() - if value in ("true", "yes", "1"): + if value in ("true", "yes", "1", "on"): return True - elif value in ("false", "no", "0"): + elif value in ("false", "no", "0", "off"): return False return default diff --git a/pype/lib/hooks.py b/pype/lib/hooks.py index 425ad36342..bb5406572e 100644 --- a/pype/lib/hooks.py +++ b/pype/lib/hooks.py @@ -55,7 +55,7 @@ def execute_hook(hook, *args, **kwargs): module.__file__ = abspath try: - with open(abspath) as f: + with open(abspath, errors='ignore') as f: six.exec_(f.read(), module.__dict__) sys.modules[abspath] = module diff --git a/pype/lib/plugin_tools.py b/pype/lib/plugin_tools.py index 535100c1c2..a78c9e525e 100644 --- a/pype/lib/plugin_tools.py +++ b/pype/lib/plugin_tools.py @@ -4,7 +4,7 @@ import os import inspect import logging -from . import get_presets +from ..api import get_project_settings log = logging.getLogger(__name__) @@ -25,7 +25,7 @@ def filter_pyblish_plugins(plugins): host = api.current_host() - presets = get_presets().get('plugins', {}) + presets = get_project_settings(os.environ['AVALON_PROJECT']) or {} # iterate over plugins for plugin in plugins[:]: @@ -53,7 +53,7 @@ def filter_pyblish_plugins(plugins): log.info('removing plugin {}'.format(plugin.__name__)) plugins.remove(plugin) else: - log.info('setting {}:{} on plugin {}'.format( + log.info('setting XXX {}:{} on plugin {}'.format( option, value, plugin.__name__)) setattr(plugin, option, value) diff --git a/pype/lib/python_module_tools.py b/pype/lib/python_module_tools.py new file mode 100644 index 0000000000..2ce2f60dca --- /dev/null +++ b/pype/lib/python_module_tools.py @@ -0,0 +1,113 @@ +import os +import sys +import types +import importlib +import inspect +import logging + +log = logging.getLogger(__name__) +PY3 = sys.version_info[0] == 3 + + +def modules_from_path(folder_path): + """Get python scripts as modules from a path. + + Arguments: + path (str): Path to folder containing python scripts. + + Returns: + List of modules. + """ + + folder_path = os.path.normpath(folder_path) + + modules = [] + if not os.path.isdir(folder_path): + log.warning("Not a directory path: {}".format(folder_path)) + return modules + + for filename in os.listdir(folder_path): + # Ignore files which start with underscore + if filename.startswith("_"): + continue + + mod_name, mod_ext = os.path.splitext(filename) + if not mod_ext == ".py": + continue + + full_path = os.path.join(folder_path, filename) + if not os.path.isfile(full_path): + continue + + try: + # Prepare module object where content of file will be parsed + module = types.ModuleType(mod_name) + + if PY3: + # Use loader so module has full specs + module_loader = importlib.machinery.SourceFileLoader( + mod_name, full_path + ) + module_loader.exec_module(module) + else: + # Execute module code and store content to module + with open(full_path) as _stream: + # Execute content and store it to module object + exec(_stream.read(), module.__dict__) + + module.__file__ = full_path + + modules.append(module) + + except Exception: + log.warning( + "Failed to load path: \"{0}\"".format(full_path), + exc_info=True + ) + continue + + return modules + + +def recursive_bases_from_class(klass): + """Extract all bases from entered class.""" + result = [] + bases = klass.__bases__ + result.extend(bases) + for base in bases: + result.extend(recursive_bases_from_class(base)) + return result + + +def classes_from_module(superclass, module): + """Return plug-ins from module + + Arguments: + superclass (superclass): Superclass of subclasses to look for + module (types.ModuleType): Imported module from which to + parse valid Avalon plug-ins. + + Returns: + List of plug-ins, or empty list if none is found. + + """ + + classes = list() + for name in dir(module): + # It could be anything at this point + obj = getattr(module, name) + if not inspect.isclass(obj): + continue + + # These are subclassed from nothing, not even `object` + if not len(obj.__bases__) > 0: + continue + + # Use string comparison rather than `issubclass` + # in order to support reloading of this module. + bases = recursive_bases_from_class(obj) + if not any(base.__name__ == superclass.__name__ for base in bases): + continue + + classes.append(obj) + return classes diff --git a/pype/modules/adobe_communicator/__init__.py b/pype/modules/adobe_communicator/__init__.py deleted file mode 100644 index 4110ab69b5..0000000000 --- a/pype/modules/adobe_communicator/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .adobe_comunicator import AdobeCommunicator - - -def tray_init(tray_widget, main_widget): - return AdobeCommunicator() diff --git a/pype/modules/adobe_communicator/adobe_comunicator.py b/pype/modules/adobe_communicator/adobe_comunicator.py deleted file mode 100644 index 51cc6780c0..0000000000 --- a/pype/modules/adobe_communicator/adobe_comunicator.py +++ /dev/null @@ -1,49 +0,0 @@ -import os -import pype -from pype.api import Logger -from .lib import AdobeRestApi, PUBLISH_PATHS - -log = Logger().get_logger("AdobeCommunicator") - - -class AdobeCommunicator: - rest_api_obj = None - - def __init__(self): - self.rest_api_obj = None - - # Add "adobecommunicator" publish paths - PUBLISH_PATHS.append(os.path.sep.join( - [pype.PLUGINS_DIR, "adobecommunicator", "publish"] - )) - - def tray_start(self): - return - - def process_modules(self, modules): - # Module requires RestApiServer - rest_api_module = modules.get("RestApiServer") - if not rest_api_module: - log.warning( - "AdobeCommunicator won't work without RestApiServer." - ) - return - - # Register statics url - pype_module_root = os.environ["PYPE_MODULE_ROOT"].replace("\\", "/") - static_path = "{}/pype/hosts/premiere/ppro".format(pype_module_root) - rest_api_module.register_statics("/ppro", static_path) - - # Register rest api object for communication - self.rest_api_obj = AdobeRestApi() - - # Add Ftrack publish path if registered Ftrack mdule - if "FtrackModule" in modules: - PUBLISH_PATHS.append(os.path.sep.join( - [pype.PLUGINS_DIR, "ftrack", "publish"] - )) - - log.debug(( - f"Adobe Communicator Registered PUBLISH_PATHS" - f"> `{PUBLISH_PATHS}`" - )) diff --git a/pype/modules/adobe_communicator/lib/__init__.py b/pype/modules/adobe_communicator/lib/__init__.py deleted file mode 100644 index f918e49a60..0000000000 --- a/pype/modules/adobe_communicator/lib/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .rest_api import AdobeRestApi, PUBLISH_PATHS - -__all__ = [ - "PUBLISH_PATHS", - "AdobeRestApi" -] diff --git a/pype/modules/adobe_communicator/lib/publish.py b/pype/modules/adobe_communicator/lib/publish.py deleted file mode 100644 index b222a1bd59..0000000000 --- a/pype/modules/adobe_communicator/lib/publish.py +++ /dev/null @@ -1,57 +0,0 @@ -import os -import sys -import pype -import importlib -import pyblish.api -import pyblish.util -import avalon.api -from avalon.tools import publish -from pype.api import Logger - -log = Logger().get_logger(__name__) - - -def main(env): - # Registers pype's Global pyblish plugins - pype.install() - - # Register Host (and it's pyblish plugins) - host_name = env["AVALON_APP"] - # TODO not sure if use "pype." or "avalon." for host import - host_import_str = f"pype.hosts.{host_name}" - - try: - host_module = importlib.import_module(host_import_str) - except ModuleNotFoundError: - log.error(( - f"Host \"{host_name}\" can't be imported." - f" Import string \"{host_import_str}\" failed." - )) - return False - - avalon.api.install(host_module) - - # Register additional paths - addition_paths_str = env.get("PUBLISH_PATHS") or "" - addition_paths = addition_paths_str.split(os.pathsep) - for path in addition_paths: - path = os.path.normpath(path) - if not os.path.exists(path): - continue - - pyblish.api.register_plugin_path(path) - - # Register project specific plugins - project_name = os.environ["AVALON_PROJECT"] - project_plugins_paths = env.get("PYPE_PROJECT_PLUGINS") or "" - for path in project_plugins_paths.split(os.pathsep): - plugin_path = os.path.join(path, project_name, "plugins") - if os.path.exists(plugin_path): - pyblish.api.register_plugin_path(plugin_path) - - return publish.show() - - -if __name__ == "__main__": - result = main(os.environ) - sys.exit(not bool(result)) diff --git a/pype/modules/adobe_communicator/lib/rest_api.py b/pype/modules/adobe_communicator/lib/rest_api.py deleted file mode 100644 index 35094d10dc..0000000000 --- a/pype/modules/adobe_communicator/lib/rest_api.py +++ /dev/null @@ -1,117 +0,0 @@ -import os -import sys -import copy -from pype.modules.rest_api import RestApi, route, abort, CallbackResult -from avalon.api import AvalonMongoDB -from pype.api import config, execute, Logger - -log = Logger().get_logger("AdobeCommunicator") - -CURRENT_DIR = os.path.dirname(__file__) -PUBLISH_SCRIPT_PATH = os.path.join(CURRENT_DIR, "publish.py") - -PUBLISH_PATHS = [] - - -class AdobeRestApi(RestApi): - dbcon = AvalonMongoDB() - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.dbcon.install() - - @route("/available", "/adobe") - def available(self): - return CallbackResult() - - @route("/presets/", "/adobe") - def get_presets(self, request): - project_name = request.url_data["project_name"] - return CallbackResult(data=config.get_presets(project_name)) - - @route("/publish", "/adobe", "POST") - def publish(self, request): - """Triggers publishing script in subprocess. - - The subprocess freeze process and during publishing is not possible to - handle other requests and is possible that freeze main application. - - TODO: Freezing issue may be fixed with socket communication. - - Example url: - http://localhost:8021/adobe/publish (POST) - """ - try: - publish_env = self._prepare_publish_environments( - request.request_data - ) - except Exception as exc: - log.warning( - "Failed to prepare environments for publishing.", - exc_info=True - ) - abort(400, str(exc)) - - output_data_path = publish_env["AC_PUBLISH_OUTPATH"] - - log.info("Pyblish is running") - try: - # Trigger subprocess - # QUESTION should we check returncode? - returncode = execute( - [sys.executable, PUBLISH_SCRIPT_PATH], - env=publish_env - ) - - # Check if output file exists - if returncode != 0 or not os.path.exists(output_data_path): - abort(500, "Publishing failed") - - log.info("Pyblish have stopped") - - return CallbackResult( - data={"return_data_path": output_data_path} - ) - - except Exception: - log.warning("Publishing failed", exc_info=True) - abort(500, "Publishing failed") - - def _prepare_publish_environments(self, data): - """Prepares environments based on request data.""" - env = copy.deepcopy(os.environ) - - project_name = data["project"] - asset_name = data["asset"] - - project_doc = self.dbcon[project_name].find_one({ - "type": "project" - }) - av_asset = self.dbcon[project_name].find_one({ - "type": "asset", - "name": asset_name - }) - parents = av_asset["data"]["parents"] - hierarchy = "" - if parents: - hierarchy = "/".join(parents) - - env["AVALON_PROJECT"] = project_name - env["AVALON_ASSET"] = asset_name - env["AVALON_TASK"] = data["task"] - env["AVALON_WORKDIR"] = data["workdir"] - env["AVALON_HIERARCHY"] = hierarchy - env["AVALON_PROJECTCODE"] = project_doc["data"].get("code", "") - env["AVALON_APP"] = data["AVALON_APP"] - env["AVALON_APP_NAME"] = data["AVALON_APP_NAME"] - - env["PYBLISH_HOSTS"] = data["AVALON_APP"] - - env["PUBLISH_PATHS"] = os.pathsep.join(PUBLISH_PATHS) - - # Input and Output paths where source data and result data will be - # stored - env["AC_PUBLISH_INPATH"] = data["adobePublishJsonPathSend"] - env["AC_PUBLISH_OUTPATH"] = data["adobePublishJsonPathGet"] - - return env diff --git a/pype/modules/ftrack/actions/action_application_loader.py b/pype/modules/ftrack/actions/action_application_loader.py deleted file mode 100644 index 0291d25401..0000000000 --- a/pype/modules/ftrack/actions/action_application_loader.py +++ /dev/null @@ -1,105 +0,0 @@ -import os -import toml -import time -from pype.modules.ftrack.lib import AppAction -from avalon import lib, api -from pype.api import Logger, config - -log = Logger().get_logger(__name__) - - -def register_app(app, dbcon, session, plugins_presets): - name = app['name'] - variant = "" - try: - variant = app['name'].split("_")[1] - except Exception: - pass - - abspath = lib.which_app(app['name']) - if abspath is None: - log.error( - "'{0}' - App don't have config toml file".format(app['name']) - ) - return - - apptoml = toml.load(abspath) - - ''' REQUIRED ''' - executable = apptoml['executable'] - - ''' OPTIONAL ''' - label = apptoml.get('ftrack_label', app.get('label', name)) - icon = apptoml.get('ftrack_icon', None) - description = apptoml.get('description', None) - preactions = apptoml.get('preactions', []) - - if icon: - icon = icon.format(os.environ.get('PYPE_STATICS_SERVER', '')) - - # register action - AppAction( - session, dbcon, label, name, executable, variant, - icon, description, preactions, plugins_presets - ).register() - - if not variant: - log.info('- Variant is not set') - - -def register(session, plugins_presets={}): - from pype.lib import env_value_to_bool - if env_value_to_bool("PYPE_USE_APP_MANAGER", default=False): - return - - app_usages = ( - config.get_presets() - .get("global", {}) - .get("applications") - ) or {} - - apps = [] - missing_app_names = [] - launchers_path = os.path.join(os.environ["PYPE_CONFIG"], "launchers") - for file in os.listdir(launchers_path): - filename, ext = os.path.splitext(file) - if ext.lower() != ".toml": - continue - - app_usage = app_usages.get(filename) - if not app_usage: - if app_usage is None: - missing_app_names.append(filename) - continue - - loaded_data = toml.load(os.path.join(launchers_path, file)) - app_data = { - "name": filename, - "label": loaded_data.get("label", filename) - } - apps.append(app_data) - - if missing_app_names: - log.debug( - "Apps not defined in applications usage. ({})".format( - ", ".join(( - "\"{}\"".format(app_name) - for app_name in missing_app_names - )) - ) - ) - - dbcon = api.AvalonMongoDB() - apps = sorted(apps, key=lambda app: app["name"]) - app_counter = 0 - for app in apps: - try: - register_app(app, dbcon, session, plugins_presets) - if app_counter % 5 == 0: - time.sleep(0.1) - app_counter += 1 - except Exception as exc: - log.warning( - "\"{}\" - not a proper App ({})".format(app['name'], str(exc)), - exc_info=True - ) diff --git a/pype/modules/ftrack/actions/action_applications.py b/pype/modules/ftrack/actions/action_applications.py index a75b581ce3..cf047a658d 100644 --- a/pype/modules/ftrack/actions/action_applications.py +++ b/pype/modules/ftrack/actions/action_applications.py @@ -1,7 +1,6 @@ import os from uuid import uuid4 -from pype.api import config from pype.modules.ftrack.lib import BaseAction from pype.lib import ( ApplicationManager, @@ -205,55 +204,6 @@ class AppplicationsAction(BaseAction): "message": msg } - # TODO Move to prelaunch/afterlaunch hooks - # TODO change to settings - # Change status of task to In progress - presets = config.get_presets()["ftrack"]["ftrack_config"] - - if "status_update" in presets: - statuses = presets["status_update"] - - actual_status = entity["status"]["name"].lower() - already_tested = [] - ent_path = "/".join( - [ent["name"] for ent in entity["link"]] - ) - while True: - next_status_name = None - for key, value in statuses.items(): - if key in already_tested: - continue - if actual_status in value or "_any_" in value: - if key != "_ignore_": - next_status_name = key - already_tested.append(key) - break - already_tested.append(key) - - if next_status_name is None: - break - - try: - query = "Status where name is \"{}\"".format( - next_status_name - ) - status = session.query(query).one() - - entity["status"] = status - session.commit() - self.log.debug("Changing status to \"{}\" <{}>".format( - next_status_name, ent_path - )) - break - - except Exception: - session.rollback() - msg = ( - "Status \"{}\" in presets wasn't found" - " on Ftrack entity type \"{}\"" - ).format(next_status_name, entity.entity_type) - self.log.warning(msg) - return { "success": True, "message": "Launching {0}".format(self.label) @@ -261,7 +211,5 @@ class AppplicationsAction(BaseAction): def register(session, plugins_presets=None): - '''Register action. Called when used as an event plugin.''' - from pype.lib import env_value_to_bool - if env_value_to_bool("PYPE_USE_APP_MANAGER", default=False): - AppplicationsAction(session, plugins_presets).register() + """Register action. Called when used as an event plugin.""" + AppplicationsAction(session, plugins_presets).register() diff --git a/pype/modules/ftrack/actions/action_create_cust_attrs.py b/pype/modules/ftrack/actions/action_create_cust_attrs.py index 11931a5b30..68fae16382 100644 --- a/pype/modules/ftrack/actions/action_create_cust_attrs.py +++ b/pype/modules/ftrack/actions/action_create_cust_attrs.py @@ -1,6 +1,4 @@ -import os import collections -import toml import json import arrow import ftrack_api @@ -8,8 +6,8 @@ from pype.modules.ftrack.lib import BaseAction, statics_icon from pype.modules.ftrack.lib.avalon_sync import ( CUST_ATTR_ID_KEY, CUST_ATTR_GROUP, default_custom_attributes_definition ) -from pype.api import config -from pype.lib import ApplicationManager, env_value_to_bool +from pype.api import get_system_settings +from pype.lib import ApplicationManager """ This action creates/updates custom attributes. @@ -146,9 +144,6 @@ class CustomAttributes(BaseAction): "text", "boolean", "date", "enumerator", "dynamic enumerator", "number" ) - # Pype 3 features - use_app_manager = env_value_to_bool("PYPE_USE_APP_MANAGER", default=False) - app_manager = None def discover(self, session, entities, event): ''' @@ -171,8 +166,7 @@ class CustomAttributes(BaseAction): }) session.commit() - if self.use_app_manager: - self.app_manager = ApplicationManager() + self.app_manager = ApplicationManager() try: self.prepare_global_data(session) @@ -217,15 +211,12 @@ class CustomAttributes(BaseAction): self.groups = {} - self.presets = config.get_presets() + self.ftrack_settings = get_system_settings()["modules"]["Ftrack"] self.attrs_presets = self.prepare_attribute_pressets() def prepare_attribute_pressets(self): output = {} - - attr_presets = ( - self.presets.get("ftrack", {}).get("ftrack_custom_attributes") - ) or {} + attr_presets = self.ftrack_settings["custom_attributes"] for entity_type, preset in attr_presets.items(): # Lower entity type entity_type = entity_type.lower() @@ -391,54 +382,8 @@ class CustomAttributes(BaseAction): app_definitions.append({"empty": "< Empty >"}) return app_definitions - def application_definitions(self): - app_usages = self.presets.get("global", {}).get("applications") or {} - - app_definitions = [] - launchers_path = os.path.join(os.environ["PYPE_CONFIG"], "launchers") - - missing_app_names = [] - for file in os.listdir(launchers_path): - app_name, ext = os.path.splitext(file) - if ext.lower() != ".toml": - continue - - if not app_usages.get(app_name): - missing_app_names.append(app_name) - continue - - loaded_data = toml.load(os.path.join(launchers_path, file)) - - ftrack_label = loaded_data.get("ftrack_label") - if ftrack_label: - parts = app_name.split("_") - if len(parts) > 1: - ftrack_label = " ".join((ftrack_label, parts[-1])) - else: - ftrack_label = loaded_data.get("label", app_name) - - app_definitions.append({app_name: ftrack_label}) - - if missing_app_names: - self.log.warning( - "Apps not defined in applications usage. ({})".format( - ", ".join(( - "\"{}\"".format(app_name) - for app_name in missing_app_names - )) - ) - ) - - # Make sure there is at least one item - if not app_definitions: - app_definitions.append({"empty": "< Empty >"}) - return app_definitions - def applications_attribute(self, event): - if self.use_app_manager: - apps_data = self.app_defs_from_app_manager() - else: - apps_data = self.application_definitions() + apps_data = self.app_defs_from_app_manager() applications_custom_attr_data = { "label": "Applications", @@ -453,28 +398,13 @@ class CustomAttributes(BaseAction): } self.process_attr_data(applications_custom_attr_data, event) - def tools_from_app_manager(self): + def tools_attribute(self, event): tools_data = [] for tool_name, tool in self.app_manager.tools.items(): if tool.enabled: tools_data.append({ tool_name: tool_name }) - return tools_data - - def tools_data(self): - tool_usages = self.presets.get("global", {}).get("tools") or {} - tools_data = [] - for tool_name, usage in tool_usages.items(): - if usage: - tools_data.append({tool_name: tool_name}) - return tools_data - - def tools_attribute(self, event): - if self.use_app_manager: - tools_data = self.tools_from_app_manager() - else: - tools_data = self.tools_data() # Make sure there is at least one item if not tools_data: @@ -494,12 +424,7 @@ class CustomAttributes(BaseAction): self.process_attr_data(tools_custom_attr_data, event) def intent_attribute(self, event): - intent_key_values = ( - self.presets - .get("global", {}) - .get("intent", {}) - .get("items", {}) - ) or {} + intent_key_values = self.ftrack_settings["intent"]["items"] intent_values = [] for key, label in intent_key_values.items(): @@ -805,6 +730,9 @@ class CustomAttributes(BaseAction): return default err_msg = 'Default value is not' if type == 'number': + if isinstance(default, (str)) and default.isnumeric(): + default = float(default) + if not isinstance(default, (float, int)): raise CustAttrException('{} integer'.format(err_msg)) elif type == 'text': diff --git a/pype/modules/ftrack/actions/action_create_folders.py b/pype/modules/ftrack/actions/action_create_folders.py index e689e0260c..a131a0e35b 100644 --- a/pype/modules/ftrack/actions/action_create_folders.py +++ b/pype/modules/ftrack/actions/action_create_folders.py @@ -1,7 +1,11 @@ import os from pype.modules.ftrack.lib import BaseAction, statics_icon from avalon import lib as avalonlib -from pype.api import config, Anatomy +from pype.api import ( + Anatomy, + get_project_settings +) +from pype.lib import ApplicationManager class CreateFolders(BaseAction): @@ -93,6 +97,7 @@ class CreateFolders(BaseAction): all_entities = self.get_notask_children(entity) anatomy = Anatomy(project_name) + project_settings = get_project_settings(project_name) work_keys = ["work", "folder"] work_template = anatomy.templates @@ -106,10 +111,13 @@ class CreateFolders(BaseAction): publish_template = publish_template[key] publish_has_apps = "{app" in publish_template - presets = config.get_presets() - app_presets = presets.get("tools", {}).get("sw_folders") - cached_apps = {} + tools_settings = project_settings["global"]["tools"] + app_presets = tools_settings["Workfiles"]["sw_folders"] + app_manager_apps = None + if app_presets and (work_has_apps or publish_has_apps): + app_manager_apps = ApplicationManager().applications + cached_apps = {} collected_paths = [] for entity in all_entities: if entity.entity_type.lower() == "project": @@ -140,18 +148,20 @@ class CreateFolders(BaseAction): task_data["task"] = child["name"] apps = [] - if app_presets and (work_has_apps or publish_has_apps): - possible_apps = app_presets.get(task_type_name, []) - for app in possible_apps: - if app in cached_apps: - app_dir = cached_apps[app] + if app_manager_apps: + possible_apps = app_presets.get(task_type_name) or [] + for app_name in possible_apps: + + if app_name in cached_apps: + apps.append(cached_apps[app_name]) + continue + + app_def = app_manager_apps.get(app_name) + if app_def and app_def.is_host: + app_dir = app_def.host_name else: - try: - app_data = avalonlib.get_application(app) - app_dir = app_data["application_dir"] - except ValueError: - app_dir = app - cached_apps[app] = app_dir + app_dir = app_name + cached_apps[app_name] = app_dir apps.append(app_dir) # Template wok diff --git a/pype/modules/ftrack/actions/action_create_project_structure.py b/pype/modules/ftrack/actions/action_create_project_structure.py index 22190c16db..0815f82a69 100644 --- a/pype/modules/ftrack/actions/action_create_project_structure.py +++ b/pype/modules/ftrack/actions/action_create_project_structure.py @@ -2,7 +2,7 @@ import os import re from pype.modules.ftrack.lib import BaseAction, statics_icon -from pype.api import config, Anatomy +from pype.api import Anatomy, get_project_settings class CreateProjectFolders(BaseAction): @@ -69,25 +69,26 @@ class CreateProjectFolders(BaseAction): return True def launch(self, session, entities, event): - entity = entities[0] - project = self.get_project_from_entity(entity) - project_folder_presets = ( - config.get_presets() - .get("tools", {}) - .get("project_folder_structure") + # Get project entity + project_entity = self.get_project_from_entity(entities[0]) + # Load settings for project + project_name = project_entity["full_name"] + project_settings = get_project_settings(project_name) + project_folder_structure = ( + project_settings["global"]["project_folder_structure"] ) - if not project_folder_presets: + if not project_folder_structure: return { "success": False, - "message": "Project structure presets are not set." + "message": "Project structure is not set." } try: # Get paths based on presets - basic_paths = self.get_path_items(project_folder_presets) - anatomy = Anatomy(project["full_name"]) - self.create_folders(basic_paths, entity, project, anatomy) - self.create_ftrack_entities(basic_paths, project) + basic_paths = self.get_path_items(project_folder_structure) + anatomy = Anatomy(project_entity["full_name"]) + self.create_folders(basic_paths, project_entity, anatomy) + self.create_ftrack_entities(basic_paths, project_entity) except Exception as exc: session.rollback() @@ -219,7 +220,7 @@ class CreateProjectFolders(BaseAction): output.append(os.path.normpath(os.path.sep.join(clean_items))) return output - def create_folders(self, basic_paths, entity, project, anatomy): + def create_folders(self, basic_paths, project, anatomy): roots_paths = [] if isinstance(anatomy.roots, dict): for root in anatomy.roots: diff --git a/pype/modules/ftrack/actions/action_delivery.py b/pype/modules/ftrack/actions/action_delivery.py index 8812ce9bc7..1beebe3e31 100644 --- a/pype/modules/ftrack/actions/action_delivery.py +++ b/pype/modules/ftrack/actions/action_delivery.py @@ -396,6 +396,13 @@ class Delivery(BaseAction): session.commit() self.db_con.uninstall() + if job["status"] == "failed": + return { + "success": False, + "message": "Delivery failed. Check logs for more information." + } + return True + def real_launch(self, session, entities, event): self.log.info("Delivery action just started.") report_items = collections.defaultdict(list) diff --git a/pype/modules/ftrack/actions/action_rv.py b/pype/modules/ftrack/actions/action_rv.py index f9aeb87f71..eeb5672047 100644 --- a/pype/modules/ftrack/actions/action_rv.py +++ b/pype/modules/ftrack/actions/action_rv.py @@ -3,7 +3,6 @@ import subprocess import traceback import json -from pype.api import config from pype.modules.ftrack.lib import BaseAction, statics_icon import ftrack_api from avalon import io, api @@ -11,7 +10,6 @@ from avalon import io, api class RVAction(BaseAction): """ Launch RV action """ - ignore_me = "rv" not in config.get_presets() identifier = "rv.launch.action" label = "rv" description = "rv Launcher" @@ -19,6 +17,8 @@ class RVAction(BaseAction): type = 'Application' + allowed_types = ["img", "mov", "exr", "mp4"] + def __init__(self, session, plugins_presets): """ Constructor @@ -26,36 +26,30 @@ class RVAction(BaseAction): :type session: :class:`ftrack_api.Session` """ super().__init__(session, plugins_presets) - self.rv_path = None - self.config_data = None + + # QUESTION load RV application data from AppplicationManager? + rv_path = None # RV_HOME should be set if properly installed if os.environ.get('RV_HOME'): - self.rv_path = os.path.join( + rv_path = os.path.join( os.environ.get('RV_HOME'), 'bin', 'rv' ) - else: - # if not, fallback to config file location - if "rv" in config.get_presets(): - self.config_data = config.get_presets()['rv']['config'] - self.set_rv_path() + if not os.path.exists(rv_path): + rv_path = None - if self.rv_path is None: - return + if not rv_path: + self.log.info("RV path was not found.") + self.ignore_me = True - self.allowed_types = self.config_data.get( - 'file_ext', ["img", "mov", "exr", "mp4"] - ) + self.rv_path = rv_path def discover(self, session, entities, event): """Return available actions based on *event*. """ return True - def set_rv_path(self): - self.rv_path = self.config_data.get("rv_path") - def preregister(self): if self.rv_path is None: return ( diff --git a/pype/modules/ftrack/events/event_user_assigment.py b/pype/modules/ftrack/events/event_user_assigment.py index 19a67b745f..9b0dfe84d1 100644 --- a/pype/modules/ftrack/events/event_user_assigment.py +++ b/pype/modules/ftrack/events/event_user_assigment.py @@ -8,7 +8,7 @@ from avalon.api import AvalonMongoDB from bson.objectid import ObjectId -from pype.api import config, Anatomy +from pype.api import Anatomy, get_project_settings class UserAssigmentEvent(BaseEvent): @@ -173,26 +173,50 @@ class UserAssigmentEvent(BaseEvent): return t_data def launch(self, session, event): - # load shell scripts presets - presets = config.get_presets()['ftrack'].get("user_assigment_event") - if not presets: + if not event.get("data"): return - for entity in event.get('data', {}).get('entities', []): - if entity.get('entity_type') != 'Appointment': + + entities_info = event["data"].get("entities") + if not entities_info: + return + + # load shell scripts presets + tmp_by_project_name = {} + for entity_info in entities_info: + if entity_info.get('entity_type') != 'Appointment': continue - task, user = self._get_task_and_user(session, - entity.get('action'), - entity.get('changes')) + task_entity, user_entity = self._get_task_and_user( + session, + entity_info.get('action'), + entity_info.get('changes') + ) - if not task or not user: - self.log.error( - 'Task or User was not found.') + if not task_entity or not user_entity: + self.log.error("Task or User was not found.") continue - data = self._get_template_data(task) # format directories to pass to shell script - anatomy = Anatomy(data["project"]["name"]) + project_name = task_entity["project"]["full_name"] + project_data = tmp_by_project_name.get(project_name) or {} + if "scripts_by_action" not in project_data: + project_settings = get_project_settings(project_name) + _settings = ( + project_settings["ftrack"]["events"]["user_assignment"] + ) + project_data["scripts_by_action"] = _settings.get("scripts") + tmp_by_project_name[project_name] = project_data + + scripts_by_action = project_data["scripts_by_action"] + if not scripts_by_action: + continue + + if "anatomy" not in project_data: + project_data["anatomy"] = Anatomy(project_name) + tmp_by_project_name[project_name] = project_data + + anatomy = project_data["anatomy"] + data = self._get_template_data(task_entity) anatomy_filled = anatomy.format(data) # formatting work dir is easiest part as we can use whole path work_dir = anatomy_filled["work"]["folder"] @@ -201,8 +225,10 @@ class UserAssigmentEvent(BaseEvent): publish = anatomy_filled["publish"]["folder"] # now find path to {asset} - m = re.search("(^.+?{})".format(data['asset']), - publish) + m = re.search( + "(^.+?{})".format(data["asset"]), + publish + ) if not m: msg = 'Cannot get part of publish path {}'.format(publish) @@ -213,12 +239,13 @@ class UserAssigmentEvent(BaseEvent): } publish_dir = m.group(1) - for script in presets.get(entity.get('action')): - self.log.info( - '[{}] : running script for user {}'.format( - entity.get('action'), user["username"])) - self._run_script(script, [user["username"], - work_dir, publish_dir]) + username = user_entity["username"] + event_entity_action = entity_info["action"] + for script in scripts_by_action.get(event_entity_action): + self.log.info(( + "[{}] : running script for user {}" + ).format(event_entity_action, username)) + self._run_script(script, [username, work_dir, publish_dir]) return True diff --git a/pype/modules/ftrack/events/event_version_to_task_statuses.py b/pype/modules/ftrack/events/event_version_to_task_statuses.py index fdb48cbc37..0ea72be1cb 100644 --- a/pype/modules/ftrack/events/event_version_to_task_statuses.py +++ b/pype/modules/ftrack/events/event_version_to_task_statuses.py @@ -1,12 +1,8 @@ from pype.modules.ftrack import BaseEvent -from pype.api import config +from pype.api import get_project_settings class VersionToTaskStatus(BaseEvent): - - # Presets usage - default_status_mapping = {} - def launch(self, session, event): '''Propagates status from version to task when changed''' @@ -48,14 +44,19 @@ class VersionToTaskStatus(BaseEvent): version_status_orig = version_status["name"] + # Get entities necessary for processing + version = session.get("AssetVersion", entity["entityId"]) + task = version.get("task") + if not task: + continue + + project_entity = self.get_project_from_entity(task) + project_name = project_entity["full_name"] + project_settings = get_project_settings(project_name) + # Load status mapping from presets status_mapping = ( - config.get_presets() - .get("ftrack", {}) - .get("ftrack_config", {}) - .get("status_version_to_task") - ) or self.default_status_mapping - + project_settings["ftrack"]["events"]["status_version_to_task"]) # Skip if mapping is empty if not status_mapping: continue @@ -78,16 +79,10 @@ class VersionToTaskStatus(BaseEvent): # Lower all names from presets new_status_names = [name.lower() for name in new_status_names] - # Get entities necessary for processing - version = session.get("AssetVersion", entity["entityId"]) - task = version.get("task") - if not task: - continue - if version["asset"]["type"]["short"].lower() == "scene": continue - project_schema = task["project"]["project_schema"] + project_schema = project_entity["project_schema"] # Get all available statuses for Task statuses = project_schema.get_statuses("Task", task["type_id"]) # map lowered status name with it's object diff --git a/pype/modules/ftrack/ftrack_server/ftrack_server.py b/pype/modules/ftrack/ftrack_server/ftrack_server.py index 92f3c0b3a0..ec39bdc50f 100644 --- a/pype/modules/ftrack/ftrack_server/ftrack_server.py +++ b/pype/modules/ftrack/ftrack_server/ftrack_server.py @@ -2,11 +2,13 @@ import os import sys import types import importlib -import ftrack_api import time import logging import inspect -from pype.api import Logger, config + +import ftrack_api + +from pype.api import Logger log = Logger().get_logger(__name__) @@ -109,9 +111,8 @@ class FtrackServer: key = "user" if self.server_type.lower() == "event": key = "server" - plugins_presets = config.get_presets().get( - "ftrack", {} - ).get("plugins", {}).get(key, {}) + # TODO replace with settings or get rid of passing the dictionary + plugins_presets = {} function_counter = 0 for function_dict in register_functions_dict: diff --git a/pype/modules/ftrack/ftrack_server/sub_event_processor.py b/pype/modules/ftrack/ftrack_server/sub_event_processor.py index 4a3241dd4f..c719c8fd08 100644 --- a/pype/modules/ftrack/ftrack_server/sub_event_processor.py +++ b/pype/modules/ftrack/ftrack_server/sub_event_processor.py @@ -9,7 +9,7 @@ from pype.modules.ftrack.ftrack_server.lib import ( SocketSession, ProcessEventHub, TOPIC_STATUS_SERVER ) import ftrack_api -from pype.api import Logger, config +from pype.api import Logger log = Logger().get_logger("Event processor") @@ -56,32 +56,16 @@ def register(session): def clockify_module_registration(): - module_name = "Clockify" - - menu_items = config.get_presets()["tray"]["menu_items"] - if not menu_items["item_usage"][module_name]: - return - api_key = os.environ.get("CLOCKIFY_API_KEY") if not api_key: log.warning("Clockify API key is not set.") return workspace_name = os.environ.get("CLOCKIFY_WORKSPACE") - if not workspace_name: - workspace_name = ( - menu_items - .get("attributes", {}) - .get(module_name, {}) - .get("workspace_name", {}) - ) - if not workspace_name: log.warning("Clockify Workspace is not set.") return - os.environ["CLOCKIFY_WORKSPACE"] = workspace_name - from pype.modules.clockify.constants import CLOCKIFY_FTRACK_SERVER_PATH current = os.environ.get("FTRACK_EVENTS_PATH") or "" diff --git a/pype/modules/ftrack/lib/__init__.py b/pype/modules/ftrack/lib/__init__.py index a52e73d10f..3890eacf90 100644 --- a/pype/modules/ftrack/lib/__init__.py +++ b/pype/modules/ftrack/lib/__init__.py @@ -3,7 +3,6 @@ from . import credentials from .ftrack_base_handler import BaseHandler from .ftrack_event_handler import BaseEvent from .ftrack_action_handler import BaseAction, ServerAction, statics_icon -from .ftrack_app_handler import AppAction __all__ = ( "avalon_sync", @@ -12,6 +11,5 @@ __all__ = ( "BaseEvent", "BaseAction", "ServerAction", - "statics_icon", - "AppAction" + "statics_icon" ) diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index 7ff5283d6a..cb34ce918d 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -8,16 +8,13 @@ import copy from avalon.api import AvalonMongoDB import avalon -import avalon.api -from avalon.vendor import toml -from pype.api import Logger, Anatomy +from pype.api import Logger, Anatomy, get_anatomy_settings from bson.objectid import ObjectId from bson.errors import InvalidId from pymongo import UpdateOne import ftrack_api -from pype.api import config - +from pype.lib import ApplicationManager log = Logger().get_logger(__name__) @@ -175,40 +172,29 @@ def get_avalon_project_template(project_name): def get_project_apps(in_app_list): - """ - Returns metadata information about apps in 'in_app_list' enhanced - from toml files. + """ Application definitions for app name. + Args: in_app_list: (list) - names of applications Returns: - tuple (list, dictionary) - list of dictionaries about apps - dictionary of warnings + tuple (list, dictionary) - list of dictionaries with apps definitions + dictionary of warnings """ apps = [] - # TODO report - missing_toml_msg = "Missing config file for application" - error_msg = ( - "Unexpected error happend during preparation of application" - ) warnings = collections.defaultdict(list) - for app in in_app_list: - try: - toml_path = avalon.lib.which_app(app) - if not toml_path: - log.warning(missing_toml_msg + ' "{}"'.format(app)) - warnings[missing_toml_msg].append(app) - continue + missing_app_msg = "Missing definition of application" + application_manager = ApplicationManager() + for app_name in in_app_list: + app = application_manager.applications.get(app_name) + if app: apps.append({ - "name": app, - "label": toml.load(toml_path)["label"] + "name": app_name, + "label": app.full_label }) - except Exception: - warnings[error_msg].append(app) - log.warning(( - "Error has happened during preparing application \"{}\"" - ).format(app), exc_info=True) + else: + warnings[missing_app_msg].append(app_name) return apps, warnings @@ -289,28 +275,6 @@ def get_hierarchical_attributes(session, entity, attr_names, attr_defaults={}): return hier_values -def get_task_short_name(task_type): - """ - Returns short name (code) for 'task_type'. Short name stored in - metadata dictionary in project.config per each 'task_type'. - Could be used in anatomy, paths etc. - If no appropriate short name is found in mapping, 'task_type' is - returned back unchanged. - - Currently stores data in: - 'pype-config/presets/ftrack/project_defaults.json' - Args: - task_type: (string) - Animation | Modeling ... - - Returns: - (string) - anim | model ... - """ - presets = config.get_presets()['ftrack']['project_defaults']\ - .get("task_short_names") - - return presets.get(task_type, task_type) - - class SyncEntitiesFactory: dbcon = AvalonMongoDB() @@ -1131,6 +1095,13 @@ class SyncEntitiesFactory: ) def prepare_ftrack_ent_data(self): + project_name = self.entities_dict[self.ft_project_id]["name"] + project_anatomy_data = get_anatomy_settings(project_name) + + task_type_mapping = ( + project_anatomy_data["attributes"]["task_short_names"] + ) + not_set_ids = [] for id, entity_dict in self.entities_dict.items(): entity = entity_dict["entity"] @@ -1167,10 +1138,12 @@ class SyncEntitiesFactory: continue self.report_items["warning"][msg] = items tasks = {} - for tt in task_types: - tasks[tt["name"]] = { - "short_name": get_task_short_name(tt["name"]) - } + for task_type in task_types: + task_type_name = task_type["name"] + short_name = task_type_mapping.get(task_type_name) + tasks[task_type_name] = { + "short_name": short_name or task_type_name + } self.entities_dict[id]["final_entity"]["config"] = { "tasks": tasks, "apps": proj_apps diff --git a/pype/modules/ftrack/lib/ftrack_app_handler.py b/pype/modules/ftrack/lib/ftrack_app_handler.py deleted file mode 100644 index 004207a3a5..0000000000 --- a/pype/modules/ftrack/lib/ftrack_app_handler.py +++ /dev/null @@ -1,227 +0,0 @@ -from pype import lib as pypelib -from pype.api import config -from .ftrack_action_handler import BaseAction - - -class AppAction(BaseAction): - """Application Action class. - - Args: - session (ftrack_api.Session): Session where action will be registered. - label (str): A descriptive string identifing your action. - varaint (str, optional): To group actions together, give them the same - label and specify a unique variant per action. - identifier (str): An unique identifier for app. - description (str): A verbose descriptive text for you action. - icon (str): Url path to icon which will be shown in Ftrack web. - """ - - type = "Application" - preactions = ["start.timer"] - - def __init__( - self, session, dbcon, label, name, executable, variant=None, - icon=None, description=None, preactions=[], plugins_presets={} - ): - self.label = label - self.identifier = name - self.executable = executable - self.variant = variant - self.icon = icon - self.description = description - self.preactions.extend(preactions) - - self.dbcon = dbcon - - super().__init__(session, plugins_presets) - if label is None: - raise ValueError("Action missing label.") - if name is None: - raise ValueError("Action missing identifier.") - if executable is None: - raise ValueError("Action missing executable.") - - def register(self): - """Registers the action, subscribing the discover and launch topics.""" - - discovery_subscription = ( - "topic=ftrack.action.discover and source.user.username={0}" - ).format(self.session.api_user) - - self.session.event_hub.subscribe( - discovery_subscription, - self._discover, - priority=self.priority - ) - - launch_subscription = ( - "topic=ftrack.action.launch" - " and data.actionIdentifier={0}" - " and source.user.username={1}" - ).format( - self.identifier, - self.session.api_user - ) - self.session.event_hub.subscribe( - launch_subscription, - self._launch - ) - - def discover(self, session, entities, event): - """Return true if we can handle the selected entities. - - Args: - session (ftrack_api.Session): Helps to query necessary data. - entities (list): Object of selected entities. - event (ftrack_api.Event): Ftrack event causing discover callback. - """ - - if ( - len(entities) != 1 - or entities[0].entity_type.lower() != "task" - ): - return False - - entity = entities[0] - if entity["parent"].entity_type.lower() == "project": - return False - - avalon_project_apps = event["data"].get("avalon_project_apps", None) - avalon_project_doc = event["data"].get("avalon_project_doc", None) - if avalon_project_apps is None: - if avalon_project_doc is None: - ft_project = self.get_project_from_entity(entity) - project_name = ft_project["full_name"] - - self.dbcon.install() - database = self.dbcon.database - avalon_project_doc = database[project_name].find_one({ - "type": "project" - }) or False - event["data"]["avalon_project_doc"] = avalon_project_doc - - if not avalon_project_doc: - return False - - project_apps_config = avalon_project_doc["config"].get("apps", []) - avalon_project_apps = [ - app["name"] for app in project_apps_config - ] or False - event["data"]["avalon_project_apps"] = avalon_project_apps - - if not avalon_project_apps: - return False - - return self.identifier in avalon_project_apps - - def _launch(self, event): - entities = self._translate_event(event) - - preactions_launched = self._handle_preactions( - self.session, event - ) - if preactions_launched is False: - return - - response = self.launch(self.session, entities, event) - - return self._handle_result(response) - - def launch(self, session, entities, event): - """Callback method for the custom action. - - return either a bool (True if successful or False if the action failed) - or a dictionary with they keys `message` and `success`, the message - should be a string and will be displayed as feedback to the user, - success should be a bool, True if successful or False if the action - failed. - - *session* is a `ftrack_api.Session` instance - - *entities* is a list of tuples each containing the entity type and - the entity id. If the entity is a hierarchical you will always get - the entity type TypedContext, once retrieved through a get operation - you will have the "real" entity type ie. example Shot, Sequence - or Asset Build. - - *event* the unmodified original event - """ - - entity = entities[0] - - task_name = entity["name"] - asset_name = entity["parent"]["name"] - project_name = entity["project"]["full_name"] - try: - pypelib.launch_application( - project_name, asset_name, task_name, self.identifier - ) - - except pypelib.ApplicationLaunchFailed as exc: - self.log.error(str(exc)) - return { - "success": False, - "message": str(exc) - } - - except Exception: - msg = "Unexpected failure of application launch {}".format( - self.label - ) - self.log.error(msg, exc_info=True) - return { - "success": False, - "message": msg - } - - # Change status of task to In progress - presets = config.get_presets()["ftrack"]["ftrack_config"] - - if "status_update" in presets: - statuses = presets["status_update"] - - actual_status = entity["status"]["name"].lower() - already_tested = [] - ent_path = "/".join( - [ent["name"] for ent in entity["link"]] - ) - while True: - next_status_name = None - for key, value in statuses.items(): - if key in already_tested: - continue - if actual_status in value or "_any_" in value: - if key != "_ignore_": - next_status_name = key - already_tested.append(key) - break - already_tested.append(key) - - if next_status_name is None: - break - - try: - query = "Status where name is \"{}\"".format( - next_status_name - ) - status = session.query(query).one() - - entity["status"] = status - session.commit() - self.log.debug("Changing status to \"{}\" <{}>".format( - next_status_name, ent_path - )) - break - - except Exception: - session.rollback() - msg = ( - "Status \"{}\" in presets wasn't found" - " on Ftrack entity type \"{}\"" - ).format(next_status_name, entity.entity_type) - self.log.warning(msg) - - return { - "success": True, - "message": "Launching {0}".format(self.label) - } diff --git a/pype/modules/websocket_server/stubs/aftereffects_server_stub.py b/pype/modules/websocket_server/stubs/aftereffects_server_stub.py index 84dce39a41..0b8c54e884 100644 --- a/pype/modules/websocket_server/stubs/aftereffects_server_stub.py +++ b/pype/modules/websocket_server/stubs/aftereffects_server_stub.py @@ -252,6 +252,9 @@ class AfterEffectsServerStub(): Args: item_id (int): + Returns: + (namedtuple) + """ res = self.websocketserver.call(self.client.call ('AfterEffects.get_work_area', @@ -305,6 +308,35 @@ class AfterEffectsServerStub(): image_path=project_path, as_copy=as_copy)) + def get_render_info(self): + """ Get render queue info for render purposes + + Returns: + (namedtuple): with 'file_name' field + """ + res = self.websocketserver.call(self.client.call + ('AfterEffects.get_render_info')) + + records = self._to_records(res) + if records: + return records.pop() + + log.debug("Render queue needs to have file extension in 'Output to'") + + def get_audio_url(self, item_id): + """ Get audio layer absolute url for comp + + Args: + item_id (int): composition id + Returns: + (str): absolute path url + """ + res = self.websocketserver.call(self.client.call + ('AfterEffects.get_audio_url', + item_id=item_id)) + + return res + def close(self): self.client.close() diff --git a/pype/modules_manager.py b/pype/modules_manager.py index 6538187ea9..72023500e4 100644 --- a/pype/modules_manager.py +++ b/pype/modules_manager.py @@ -3,7 +3,7 @@ import inspect import pype.modules from pype.modules import PypeModule -from pype.settings import system_settings +from pype.settings import get_system_settings from pype.api import Logger @@ -24,7 +24,7 @@ class PypeModuleManager: return environments def find_pype_modules(self): - settings = system_settings() + settings = get_system_settings() modules = [] dirpath = os.path.dirname(pype.modules.__file__) for module_name in os.listdir(dirpath): diff --git a/pype/plugin.py b/pype/plugin.py index a169e82beb..1b769cd1f1 100644 --- a/pype/plugin.py +++ b/pype/plugin.py @@ -2,7 +2,7 @@ import tempfile import os import pyblish.api -from pype.api import config +from pype.api import get_project_settings import inspect ValidatePipelineOrder = pyblish.api.ValidatorOrder + 0.05 @@ -24,12 +24,14 @@ def imprint_attributes(plugin): plugin_host = file.split(os.path.sep)[-3:-2][0] plugin_name = type(plugin).__name__ try: - config_data = config.get_presets()['plugins'][plugin_host][plugin_kind][plugin_name] # noqa: E501 + settings = get_project_settings(os.environ['AVALON_PROJECT']) + settings_data = settings[plugin_host][plugin_kind][plugin_name] # noqa: E501 + print(settings_data) except KeyError: print("preset not found") return - for option, value in config_data.items(): + for option, value in settings_data.items(): if option == "enabled" and value is False: setattr(plugin, "active", False) else: diff --git a/pype/plugins/aftereffects/publish/collect_audio.py b/pype/plugins/aftereffects/publish/collect_audio.py new file mode 100644 index 0000000000..37938eb6a6 --- /dev/null +++ b/pype/plugins/aftereffects/publish/collect_audio.py @@ -0,0 +1,27 @@ +import os + +import pyblish.api + +from avalon import aftereffects + + +class CollectAudio(pyblish.api.ContextPlugin): + """Inject audio file url for rendered composition into context. + Needs to run AFTER 'collect_render'. Use collected comp_id to check + if there is an AVLayer in this composition + """ + + order = pyblish.api.CollectorOrder + 0.499 + label = "Collect Audio" + hosts = ["aftereffects"] + + def process(self, context): + for instance in context: + if instance.data["family"] == 'render.farm': + comp_id = instance.data["comp_id"] + if not comp_id: + self.log.debug("No comp_id filled in instance") + return + context.data["audioFile"] = os.path.normpath( + aftereffects.stub().get_audio_url(comp_id) + ).replace("\\", "/") diff --git a/pype/plugins/aftereffects/publish/collect_render.py b/pype/plugins/aftereffects/publish/collect_render.py index 13ffc3f208..fbe392d52b 100644 --- a/pype/plugins/aftereffects/publish/collect_render.py +++ b/pype/plugins/aftereffects/publish/collect_render.py @@ -11,6 +11,7 @@ from avalon import aftereffects class AERenderInstance(RenderInstance): # extend generic, composition name is needed comp_name = attr.ib(default=None) + comp_id = attr.ib(default=None) class CollectAERender(abstract_collect_render.AbstractCollectRender): @@ -39,13 +40,11 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): continue work_area_info = aftereffects.stub().get_work_area(int(item_id)) - frameStart = round(float(work_area_info.workAreaStart) * - float(work_area_info.frameRate)) + frameStart = work_area_info.workAreaStart - frameEnd = round(float(work_area_info.workAreaStart) * - float(work_area_info.frameRate) + + frameEnd = round(work_area_info.workAreaStart + float(work_area_info.workAreaDuration) * - float(work_area_info.frameRate)) + float(work_area_info.frameRate)) - 1 if inst["family"] == "render" and inst["active"]: instance = AERenderInstance( @@ -83,6 +82,7 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): raise ValueError("There is no composition for item {}". format(item_id)) instance.comp_name = comp.name + instance.comp_id = item_id instance._anatomy = context.data["anatomy"] instance.anatomyData = context.data["anatomyData"] @@ -108,18 +108,29 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): start = render_instance.frameStart end = render_instance.frameEnd + # pull file name from Render Queue Output module + render_q = aftereffects.stub().get_render_info() + _, ext = os.path.splitext(os.path.basename(render_q.file_name)) base_dir = self._get_output_dir(render_instance) expected_files = [] - for frame in range(start, end + 1): - path = os.path.join(base_dir, "{}_{}_{}.{}.{}".format( - render_instance.asset, - render_instance.subset, - "v{:03d}".format(render_instance.version), - str(frame).zfill(self.padding_width), - self.rendered_extension - )) + if "#" not in render_q.file_name: # single frame (mov)W + path = os.path.join(base_dir, "{}_{}_{}.{}".format( + render_instance.asset, + render_instance.subset, + "v{:03d}".format(render_instance.version), + ext.replace('.', '') + )) expected_files.append(path) - + else: + for frame in range(start, end + 1): + path = os.path.join(base_dir, "{}_{}_{}.{}.{}".format( + render_instance.asset, + render_instance.subset, + "v{:03d}".format(render_instance.version), + str(frame).zfill(self.padding_width), + ext.replace('.', '') + )) + expected_files.append(path) return expected_files def _get_output_dir(self, render_instance): diff --git a/pype/plugins/aftereffects/publish/extract_save_scene.py b/pype/plugins/aftereffects/publish/extract_save_scene.py new file mode 100644 index 0000000000..e19065d086 --- /dev/null +++ b/pype/plugins/aftereffects/publish/extract_save_scene.py @@ -0,0 +1,14 @@ +import pype.api +from avalon import aftereffects + + +class ExtractSaveScene(pype.api.Extractor): + """Save scene before extraction.""" + + order = pype.api.Extractor.order - 0.49 + label = "Extract Save Scene" + hosts = ["aftereffects"] + families = ["workfile"] + + def process(self, instance): + aftereffects.stub().save() diff --git a/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py b/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py index 15d9e216fb..9414bdd39d 100644 --- a/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py +++ b/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py @@ -18,15 +18,18 @@ class DeadlinePluginInfo(): ProjectPath = attr.ib(default=None) AWSAssetFile0 = attr.ib(default=None) Version = attr.ib(default=None) + MultiProcess = attr.ib(default=None) class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): label = "Submit AE to Deadline" - order = pyblish.api.IntegratorOrder + order = pyblish.api.IntegratorOrder + 0.1 hosts = ["aftereffects"] families = ["render.farm"] # cannot be "render' as that is integrated - use_published = False + use_published = True + + chunk_size = 1000000 def get_job_info(self): dln_job_info = DeadlineJobInfo(Plugin="AfterEffects") @@ -39,9 +42,12 @@ class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline dln_job_info.Plugin = "AfterEffects" dln_job_info.UserName = context.data.get( "deadlineUser", getpass.getuser()) - frame_range = "{}-{}".format(self._instance.data["frameStart"], - self._instance.data["frameEnd"]) - dln_job_info.Frames = frame_range + if self._instance.data["frameEnd"] > self._instance.data["frameStart"]: + frame_range = "{}-{}".format(self._instance.data["frameStart"], + self._instance.data["frameEnd"]) + dln_job_info.Frames = frame_range + + dln_job_info.ChunkSize = self.chunk_size dln_job_info.OutputFilename = \ os.path.basename(self._instance.data["expectedFiles"][0]) dln_job_info.OutputDirectory = \ @@ -77,21 +83,25 @@ class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline script_path = context.data["currentFile"] render_path = self._instance.data["expectedFiles"][0] - # replace frame info ('000001') with Deadline's required '[#######]' - # expects filename in format project_asset_subset_version.FRAME.ext - render_dir = os.path.dirname(render_path) - file_name = os.path.basename(render_path) - arr = file_name.split('.') - assert len(arr) == 3, \ - "Unable to parse frames from {}".format(file_name) - hashed = '[{}]'.format(len(arr[1]) * "#") - render_path = os.path.join(render_dir, - '{}.{}.{}'.format(arr[0], hashed, arr[2])) + if len(self._instance.data["expectedFiles"]) > 1: + # replace frame ('000001') with Deadline's required '[#######]' + # expects filename in format project_asset_subset_version.FRAME.ext + render_dir = os.path.dirname(render_path) + file_name = os.path.basename(render_path) + arr = file_name.split('.') + assert len(arr) == 3, \ + "Unable to parse frames from {}".format(file_name) + hashed = '[{}]'.format(len(arr[1]) * "#") + render_path = os.path.join(render_dir, + '{}.{}.{}'.format(arr[0], hashed, + arr[2])) + + deadline_plugin_info.MultiProcess = True deadline_plugin_info.Comp = self._instance.data["comp_name"] deadline_plugin_info.Version = "17.5" - deadline_plugin_info.SceneFile = script_path + deadline_plugin_info.SceneFile = self.scene_path deadline_plugin_info.Output = render_path.replace("\\", "/") return attr.asdict(deadline_plugin_info) diff --git a/pype/plugins/ftrack/publish/collect_ftrack_api.py b/pype/plugins/ftrack/publish/collect_ftrack_api.py index bbda6da3b0..59839d7710 100644 --- a/pype/plugins/ftrack/publish/collect_ftrack_api.py +++ b/pype/plugins/ftrack/publish/collect_ftrack_api.py @@ -96,6 +96,7 @@ class CollectFtrackApi(pyblish.api.ContextPlugin): task_entity = None self.log.warning("Task name is not set.") + context.data["ftrackPythonModule"] = ftrack_api context.data["ftrackProject"] = project_entity context.data["ftrackEntity"] = asset_entity context.data["ftrackTask"] = task_entity diff --git a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py index e4496138bb..a1377cc771 100644 --- a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py +++ b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py @@ -36,6 +36,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder - 0.04 label = 'Integrate Hierarchy To Ftrack' families = ["shot"] + hosts = ["hiero"] optional = False def process(self, context): diff --git a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack_SP.py b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack_SP.py new file mode 100644 index 0000000000..ac606ed27d --- /dev/null +++ b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack_SP.py @@ -0,0 +1,331 @@ +import sys +import six +import collections +import pyblish.api +from avalon import io + +from pype.modules.ftrack.lib.avalon_sync import ( + CUST_ATTR_AUTO_SYNC, + get_pype_attr +) + + +class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): + """ + Create entities in ftrack based on collected data from premiere + Example of entry data: + { + "ProjectXS": { + "entity_type": "Project", + "custom_attributes": { + "fps": 24,... + }, + "tasks": [ + "Compositing", + "Lighting",... *task must exist as task type in project schema* + ], + "childs": { + "sq01": { + "entity_type": "Sequence", + ... + } + } + } + } + """ + + order = pyblish.api.IntegratorOrder - 0.04 + label = 'Integrate Hierarchy To Ftrack' + families = ["shot"] + hosts = ["standalonepublisher"] + optional = False + + def process(self, context): + self.context = context + if "hierarchyContext" not in self.context.data: + return + + hierarchy_context = self.context.data["hierarchyContext"] + + self.session = self.context.data["ftrackSession"] + project_name = self.context.data["projectEntity"]["name"] + query = 'Project where full_name is "{}"'.format(project_name) + project = self.session.query(query).one() + auto_sync_state = project[ + "custom_attributes"][CUST_ATTR_AUTO_SYNC] + + if not io.Session: + io.install() + + self.ft_project = None + + input_data = hierarchy_context + + # disable termporarily ftrack project's autosyncing + if auto_sync_state: + self.auto_sync_off(project) + + try: + # import ftrack hierarchy + self.import_to_ftrack(input_data) + except Exception: + raise + finally: + if auto_sync_state: + self.auto_sync_on(project) + + def import_to_ftrack(self, input_data, parent=None): + # Prequery hiearchical custom attributes + hier_custom_attributes = get_pype_attr(self.session)[1] + hier_attr_by_key = { + attr["key"]: attr + for attr in hier_custom_attributes + } + # Get ftrack api module (as they are different per python version) + ftrack_api = self.context.data["ftrackPythonModule"] + + for entity_name in input_data: + entity_data = input_data[entity_name] + entity_type = entity_data['entity_type'] + self.log.debug(entity_data) + self.log.debug(entity_type) + + if entity_type.lower() == 'project': + query = 'Project where full_name is "{}"'.format(entity_name) + entity = self.session.query(query).one() + self.ft_project = entity + self.task_types = self.get_all_task_types(entity) + + elif self.ft_project is None or parent is None: + raise AssertionError( + "Collected items are not in right order!" + ) + + # try to find if entity already exists + else: + query = ( + 'TypedContext where name is "{0}" and ' + 'project_id is "{1}"' + ).format(entity_name, self.ft_project["id"]) + try: + entity = self.session.query(query).one() + except Exception: + entity = None + + # Create entity if not exists + if entity is None: + entity = self.create_entity( + name=entity_name, + type=entity_type, + parent=parent + ) + # self.log.info('entity: {}'.format(dict(entity))) + # CUSTOM ATTRIBUTES + custom_attributes = entity_data.get('custom_attributes', []) + instances = [ + i for i in self.context if i.data['asset'] in entity['name'] + ] + for key in custom_attributes: + hier_attr = hier_attr_by_key.get(key) + # Use simple method if key is not hierarchical + if not hier_attr: + assert (key in entity['custom_attributes']), ( + 'Missing custom attribute key: `{0}` in attrs: ' + '`{1}`'.format(key, entity['custom_attributes'].keys()) + ) + + entity['custom_attributes'][key] = custom_attributes[key] + + else: + # Use ftrack operations method to set hiearchical + # attribute value. + # - this is because there may be non hiearchical custom + # attributes with different properties + entity_key = collections.OrderedDict({ + "configuration_id": hier_attr["id"], + "entity_id": entity["id"] + }) + self.session.recorded_operations.push( + ftrack_api.operation.UpdateEntityOperation( + "ContextCustomAttributeValue", + entity_key, + "value", + ftrack_api.symbol.NOT_SET, + custom_attributes[key] + ) + ) + + for instance in instances: + instance.data['ftrackEntity'] = entity + + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + self.session._configure_locations() + six.reraise(tp, value, tb) + + # TASKS + tasks = entity_data.get('tasks', []) + existing_tasks = [] + tasks_to_create = [] + for child in entity['children']: + if child.entity_type.lower() == 'task': + existing_tasks.append(child['name'].lower()) + # existing_tasks.append(child['type']['name']) + + for task_name in tasks: + task_type = tasks[task_name]["type"] + if task_name.lower() in existing_tasks: + print("Task {} already exists".format(task_name)) + continue + tasks_to_create.append((task_name, task_type)) + + for task_name, task_type in tasks_to_create: + self.create_task( + name=task_name, + task_type=task_type, + parent=entity + ) + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + self.session._configure_locations() + six.reraise(tp, value, tb) + + # Incoming links. + self.create_links(entity_data, entity) + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + self.session._configure_locations() + six.reraise(tp, value, tb) + + # Create notes. + user = self.session.query( + "User where username is \"{}\"".format(self.session.api_user) + ).first() + if user: + for comment in entity_data.get("comments", []): + entity.create_note(comment, user) + else: + self.log.warning( + "Was not able to query current User {}".format( + self.session.api_user + ) + ) + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + self.session._configure_locations() + six.reraise(tp, value, tb) + + # Import children. + if 'childs' in entity_data: + self.import_to_ftrack( + entity_data['childs'], entity) + + def create_links(self, entity_data, entity): + # Clear existing links. + for link in entity.get("incoming_links", []): + self.session.delete(link) + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + self.session._configure_locations() + six.reraise(tp, value, tb) + + # Create new links. + for input in entity_data.get("inputs", []): + input_id = io.find_one({"_id": input})["data"]["ftrackId"] + assetbuild = self.session.get("AssetBuild", input_id) + self.log.debug( + "Creating link from {0} to {1}".format( + assetbuild["name"], entity["name"] + ) + ) + self.session.create( + "TypedContextLink", {"from": assetbuild, "to": entity} + ) + + def get_all_task_types(self, project): + tasks = {} + proj_template = project['project_schema'] + temp_task_types = proj_template['_task_type_schema']['types'] + + for type in temp_task_types: + if type['name'] not in tasks: + tasks[type['name']] = type + + return tasks + + def create_task(self, name, task_type, parent): + task = self.session.create('Task', { + 'name': name, + 'parent': parent + }) + # TODO not secured!!! - check if task_type exists + self.log.info(task_type) + self.log.info(self.task_types) + task['type'] = self.task_types[task_type] + + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + self.session._configure_locations() + six.reraise(tp, value, tb) + + return task + + def create_entity(self, name, type, parent): + entity = self.session.create(type, { + 'name': name, + 'parent': parent + }) + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + self.session._configure_locations() + six.reraise(tp, value, tb) + + return entity + + def auto_sync_off(self, project): + project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = False + + self.log.info("Ftrack autosync swithed off") + + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + self.session._configure_locations() + six.reraise(tp, value, tb) + + def auto_sync_on(self, project): + + project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = True + + self.log.info("Ftrack autosync swithed on") + + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + self.session._configure_locations() + six.reraise(tp, value, tb) diff --git a/pype/plugins/global/publish/validate_custom_ftrack_attributes.py b/pype/plugins/ftrack/publish/validate_custom_ftrack_attributes.py similarity index 100% rename from pype/plugins/global/publish/validate_custom_ftrack_attributes.py rename to pype/plugins/ftrack/publish/validate_custom_ftrack_attributes.py diff --git a/pype/plugins/global/publish/collect_presets.py b/pype/plugins/global/publish/collect_presets.py index 4ffb2fc0b3..95fb4dbfad 100644 --- a/pype/plugins/global/publish/collect_presets.py +++ b/pype/plugins/global/publish/collect_presets.py @@ -8,7 +8,7 @@ Provides: """ from pyblish import api -from pype.api import config +from pype.api import get_current_project_settings class CollectPresets(api.ContextPlugin): @@ -18,23 +18,7 @@ class CollectPresets(api.ContextPlugin): label = "Collect Presets" def process(self, context): - presets = config.get_presets() - try: - # try if it is not in projects custom directory - # `{PYPE_PROJECT_CONFIGS}/[PROJECT_NAME]/init.json` - # init.json define preset names to be used - p_init = presets["init"] - presets["colorspace"] = presets["colorspace"][p_init["colorspace"]] - presets["dataflow"] = presets["dataflow"][p_init["dataflow"]] - except KeyError: - self.log.warning("No projects custom preset available...") - presets["colorspace"] = presets["colorspace"]["default"] - presets["dataflow"] = presets["dataflow"]["default"] - self.log.info( - "Presets `colorspace` and `dataflow` loaded from `default`..." - ) + project_settings = get_current_project_settings() + context.data["presets"] = project_settings - context.data["presets"] = presets - - # self.log.info(context.data["presets"]) return diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index 857119176f..d08748209d 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -280,6 +280,15 @@ class ExtractReview(pyblish.api.InstancePlugin): handles_are_set = handle_start > 0 or handle_end > 0 + with_audio = True + if ( + # Check if has `no-audio` tag + "no-audio" in output_def["tags"] + # Check if instance has ny audio in data + or not instance.data.get("audio") + ): + with_audio = False + return { "fps": float(instance.data["fps"]), "frame_start": frame_start, @@ -295,6 +304,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "resolution_height": instance.data.get("resolutionHeight"), "origin_repre": repre, "input_is_sequence": self.input_is_sequence(repre), + "with_audio": with_audio, "without_handles": without_handles, "handles_are_set": handles_are_set } @@ -389,7 +399,7 @@ class ExtractReview(pyblish.api.InstancePlugin): ffmpeg_output_args.append("-shortest") # Add audio arguments if there are any. Skipped when output are images. - if not temp_data["output_ext_is_image"]: + if not temp_data["output_ext_is_image"] and temp_data["with_audio"]: audio_in_args, audio_filters, audio_out_args = self.audio_args( instance, temp_data ) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 256bf01665..b8bf240c06 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -600,6 +600,13 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "files": os.path.basename(remainder), "stagingDir": os.path.dirname(remainder), } + if "render" in instance.get("families"): + rep.update({ + "fps": instance.get("fps"), + "tags": ["review"] + }) + self._solve_families(instance, True) + if remainder in bake_render_path: rep.update({ "fps": instance.get("fps"), diff --git a/pype/plugins/hiero/publish/extract_review_cutup.py b/pype/plugins/hiero/publish/extract_review_cutup.py index ace6bc88d3..87e584d0b0 100644 --- a/pype/plugins/hiero/publish/extract_review_cutup.py +++ b/pype/plugins/hiero/publish/extract_review_cutup.py @@ -23,6 +23,8 @@ class ExtractReviewCutUp(pype.api.Extractor): def process(self, instance): inst_data = instance.data asset = inst_data['asset'] + item = inst_data['item'] + event_number = int(item.eventNumber()) # get representation and loop them representations = inst_data["representations"] @@ -97,7 +99,12 @@ class ExtractReviewCutUp(pype.api.Extractor): index = 0 for image in collection: dst_file_num = frame_start + index - dst_file_name = head + str(padding % dst_file_num) + tail + dst_file_name = "".join([ + str(event_number), + head, + str(padding % dst_file_num), + tail + ]) src = os.path.join(staging_dir, image) dst = os.path.join(full_output_dir, dst_file_name) self.log.info("Creating temp hardlinks: {}".format(dst)) diff --git a/pype/plugins/maya/create/create_rendersetup.py b/pype/plugins/maya/create/create_rendersetup.py index 98f54f2d70..969c085ea6 100644 --- a/pype/plugins/maya/create/create_rendersetup.py +++ b/pype/plugins/maya/create/create_rendersetup.py @@ -26,10 +26,10 @@ class CreateRenderSetup(avalon.maya.Creator): # \__| | # \_____/ - # from pype.api import config + # from pype.api import get_project_settings # import maya.app.renderSetup.model.renderSetup as renderSetup - # presets = config.get_presets(project=os.environ['AVALON_PROJECT']) - # layer = presets['plugins']['maya']['create']['renderSetup']["layer"] + # settings = get_project_settings(os.environ['AVALON_PROJECT']) + # layer = settings['maya']['create']['renderSetup']["layer"] # rs = renderSetup.instance() # rs.createRenderLayer(layer) diff --git a/pype/plugins/maya/load/load_ass.py b/pype/plugins/maya/load/load_ass.py index ffe70c39e8..9b851a3757 100644 --- a/pype/plugins/maya/load/load_ass.py +++ b/pype/plugins/maya/load/load_ass.py @@ -1,7 +1,7 @@ from avalon import api import pype.hosts.maya.plugin import os -from pype.api import config +from pype.api import get_project_settings import clique @@ -74,8 +74,8 @@ class AssProxyLoader(pype.hosts.maya.plugin.ReferenceLoader): proxyShape.dso.set(path) proxyShape.aiOverrideShaders.set(0) - presets = config.get_presets(project=os.environ['AVALON_PROJECT']) - colors = presets['plugins']['maya']['load']['colors'] + settings = get_project_settings(os.environ['AVALON_PROJECT']) + colors = settings['maya']['load']['colors'] c = colors.get(family) if c is not None: @@ -196,8 +196,8 @@ class AssStandinLoader(api.Loader): label = "{}:{}".format(namespace, name) root = pm.group(name=label, empty=True) - presets = config.get_presets(project=os.environ['AVALON_PROJECT']) - colors = presets['plugins']['maya']['load']['colors'] + settings = get_project_settings(os.environ['AVALON_PROJECT']) + colors = settings['maya']['load']['colors'] c = colors.get('ass') if c is not None: diff --git a/pype/plugins/maya/load/load_gpucache.py b/pype/plugins/maya/load/load_gpucache.py index 9930dbaac6..0b3daae710 100644 --- a/pype/plugins/maya/load/load_gpucache.py +++ b/pype/plugins/maya/load/load_gpucache.py @@ -1,7 +1,7 @@ from avalon import api import pype.hosts.maya.plugin import os -from pype.api import config +from pype.api import get_project_settings reload(config) @@ -35,8 +35,8 @@ class GpuCacheLoader(api.Loader): label = "{}:{}".format(namespace, name) root = cmds.group(name=label, empty=True) - presets = config.get_presets(project=os.environ['AVALON_PROJECT']) - colors = presets['plugins']['maya']['load']['colors'] + settings = get_project_settings(os.environ['AVALON_PROJECT']) + colors = settings['maya']['load']['colors'] c = colors.get('model') if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) diff --git a/pype/plugins/maya/load/load_reference.py b/pype/plugins/maya/load/load_reference.py index dbb3cc98b2..23b3cedb55 100644 --- a/pype/plugins/maya/load/load_reference.py +++ b/pype/plugins/maya/load/load_reference.py @@ -2,7 +2,7 @@ import pype.hosts.maya.plugin from avalon import api, maya from maya import cmds import os -from pype.api import config +from pype.api import get_project_settings class ReferenceLoader(pype.hosts.maya.plugin.ReferenceLoader): @@ -77,8 +77,8 @@ class ReferenceLoader(pype.hosts.maya.plugin.ReferenceLoader): cmds.setAttr(groupName + ".displayHandle", 1) - presets = config.get_presets(project=os.environ['AVALON_PROJECT']) - colors = presets['plugins']['maya']['load']['colors'] + settings = get_project_settings(os.environ['AVALON_PROJECT']) + colors = settings['maya']['load']['colors'] c = colors.get(family) if c is not None: groupNode.useOutlinerColor.set(1) diff --git a/pype/plugins/maya/load/load_vdb_to_redshift.py b/pype/plugins/maya/load/load_vdb_to_redshift.py index 4893640b27..17c78d7165 100644 --- a/pype/plugins/maya/load/load_vdb_to_redshift.py +++ b/pype/plugins/maya/load/load_vdb_to_redshift.py @@ -1,6 +1,6 @@ from avalon import api import os -from pype.api import config +from pype.api import get_project_settings class LoadVDBtoRedShift(api.Loader): """Load OpenVDB in a Redshift Volume Shape""" @@ -55,8 +55,8 @@ class LoadVDBtoRedShift(api.Loader): label = "{}:{}".format(namespace, name) root = cmds.group(name=label, empty=True) - presets = config.get_presets(project=os.environ['AVALON_PROJECT']) - colors = presets['plugins']['maya']['load']['colors'] + settings = get_project_settings(os.environ['AVALON_PROJECT']) + colors = settings['maya']['load']['colors'] c = colors.get(family) if c is not None: diff --git a/pype/plugins/maya/load/load_vdb_to_vray.py b/pype/plugins/maya/load/load_vdb_to_vray.py index aee0ee026d..2959ef42ec 100644 --- a/pype/plugins/maya/load/load_vdb_to_vray.py +++ b/pype/plugins/maya/load/load_vdb_to_vray.py @@ -1,5 +1,5 @@ from avalon import api -from pype.api import config +from pype.api import get_project_settings import os @@ -48,8 +48,8 @@ class LoadVDBtoVRay(api.Loader): label = "{}:{}".format(namespace, name) root = cmds.group(name=label, empty=True) - presets = config.get_presets(project=os.environ['AVALON_PROJECT']) - colors = presets['plugins']['maya']['load']['colors'] + settings = get_project_settings(os.environ['AVALON_PROJECT']) + colors = settings['maya']['load']['colors'] c = colors.get(family) if c is not None: diff --git a/pype/plugins/maya/load/load_vrayproxy.py b/pype/plugins/maya/load/load_vrayproxy.py index 894ec75c32..73f02b81e4 100644 --- a/pype/plugins/maya/load/load_vrayproxy.py +++ b/pype/plugins/maya/load/load_vrayproxy.py @@ -1,6 +1,6 @@ from avalon.maya import lib from avalon import api -from pype.api import config +from pype.api import get_project_settings import os import maya.cmds as cmds @@ -47,8 +47,8 @@ class VRayProxyLoader(api.Loader): return # colour the group node - presets = config.get_presets(project=os.environ['AVALON_PROJECT']) - colors = presets['plugins']['maya']['load']['colors'] + settings = get_project_settings(os.environ['AVALON_PROJECT']) + colors = settings['maya']['load']['colors'] c = colors.get(family) if c is not None: cmds.setAttr("{0}.useOutlinerColor".format(group_node), 1) diff --git a/pype/plugins/maya/load/load_yeti_cache.py b/pype/plugins/maya/load/load_yeti_cache.py index ef0b5d5efa..19cf3920fe 100644 --- a/pype/plugins/maya/load/load_yeti_cache.py +++ b/pype/plugins/maya/load/load_yeti_cache.py @@ -9,7 +9,7 @@ from maya import cmds from avalon import api, io from avalon.maya import lib as avalon_lib, pipeline from pype.hosts.maya import lib -from pype.api import config +from pype.api import get_project_settings from pprint import pprint @@ -59,8 +59,8 @@ class YetiCacheLoader(api.Loader): group_name = "{}:{}".format(namespace, name) group_node = cmds.group(nodes, name=group_name) - presets = config.get_presets(project=os.environ['AVALON_PROJECT']) - colors = presets['plugins']['maya']['load']['colors'] + settings = get_project_settings(os.environ['AVALON_PROJECT']) + colors = settings['maya']['load']['colors'] c = colors.get(family) if c is not None: diff --git a/pype/plugins/maya/load/load_yeti_rig.py b/pype/plugins/maya/load/load_yeti_rig.py index 0604953198..3a9339c707 100644 --- a/pype/plugins/maya/load/load_yeti_rig.py +++ b/pype/plugins/maya/load/load_yeti_rig.py @@ -1,7 +1,7 @@ import os from collections import defaultdict -from pype.api import config +from pype.api import get_project_settings import pype.hosts.maya.plugin from pype.hosts.maya import lib @@ -77,8 +77,8 @@ class YetiRigLoader(pype.hosts.maya.plugin.ReferenceLoader): groupName = "{}:{}".format(namespace, name) - presets = config.get_presets(project=os.environ['AVALON_PROJECT']) - colors = presets['plugins']['maya']['load']['colors'] + settings = get_project_settings(os.environ['AVALON_PROJECT']) + colors = settings['maya']['load']['colors'] c = colors.get('yetiRig') if c is not None: diff --git a/pype/plugins/maya/publish/extract_camera_mayaScene.py b/pype/plugins/maya/publish/extract_camera_mayaScene.py index 8fce48badf..0443357ba9 100644 --- a/pype/plugins/maya/publish/extract_camera_mayaScene.py +++ b/pype/plugins/maya/publish/extract_camera_mayaScene.py @@ -102,7 +102,8 @@ class ExtractCameraMayaScene(pype.api.Extractor): def process(self, instance): """Plugin entry point.""" # get settings - ext_mapping = instance.context.data["presets"]["maya"].get("ext_mapping") # noqa: E501 + ext_mapping = (instance.context.data["presets"]["maya"] + .get("ext_mapping")) # noqa: E501 if ext_mapping: self.log.info("Looking in presets for scene type ...") # use extension mapping for first family found diff --git a/pype/plugins/maya/publish/extract_look.py b/pype/plugins/maya/publish/extract_look.py index 6bd202093f..0a7e4fb2ee 100644 --- a/pype/plugins/maya/publish/extract_look.py +++ b/pype/plugins/maya/publish/extract_look.py @@ -172,10 +172,11 @@ class ExtractLook(pype.api.Extractor): cspace = files_metadata[filepath]["color_space"] linearise = False - if cspace == "sRGB": - linearise = True - # set its file node to 'raw' as tx will be linearized - files_metadata[filepath]["color_space"] = "raw" + if do_maketx: + if cspace == "sRGB": + linearise = True + # set its file node to 'raw' as tx will be linearized + files_metadata[filepath]["color_space"] = "raw" source, mode, hash = self._process_texture( filepath, @@ -350,7 +351,7 @@ class ExtractLook(pype.api.Extractor): if existing and not force: self.log.info("Found hash in database, preparing hardlink..") source = next((p for p in existing if os.path.exists(p)), None) - if filepath: + if source: return source, HARDLINK, texture_hash else: self.log.warning( diff --git a/pype/plugins/maya/publish/submit_maya_muster.py b/pype/plugins/maya/publish/submit_maya_muster.py index ffe434048a..9c67b45721 100644 --- a/pype/plugins/maya/publish/submit_maya_muster.py +++ b/pype/plugins/maya/publish/submit_maya_muster.py @@ -11,7 +11,7 @@ from avalon.vendor import requests import pyblish.api from pype.hosts.maya import lib -from pype.api import config +from pype.api import get_system_settings # mapping between Maya renderer names and Muster template ids @@ -25,10 +25,10 @@ def _get_template_id(renderer): :rtype: int """ - templates = config.get_presets()["muster"]["templates_mapping"] + templates = get_system_settings()["modules"]["Muster"]["templates_mapping"] if not templates: - raise RuntimeError(("Muster template mapping missing in pype-config " - "`presets/muster/templates_mapping.json`")) + raise RuntimeError(("Muster template mapping missing in " + "pype-settings")) try: template_id = templates[renderer] except KeyError: diff --git a/pype/plugins/nuke/load/load_mov.py b/pype/plugins/nuke/load/load_mov.py index d252aaa09d..104f59d5be 100644 --- a/pype/plugins/nuke/load/load_mov.py +++ b/pype/plugins/nuke/load/load_mov.py @@ -4,7 +4,7 @@ import contextlib from avalon import api, io from pype.hosts.nuke import presets -from pype.api import config +from pype.api import get_project_settings @contextlib.contextmanager @@ -73,7 +73,8 @@ def add_review_presets_config(): "families": list(), "representations": list() } - review_presets = config.get_presets()["plugins"]["global"]["publish"].get( + settings = get_project_settings(io.Session["AVALON_PROJECT"]) + review_presets = settings["global"]["publish"].get( "ExtractReview", {}) outputs = review_presets.get("outputs", {}) diff --git a/pype/plugins/tvpaint/load/load_reference_image.py b/pype/plugins/tvpaint/load/load_reference_image.py index 0fa4cefc51..b3cefee4c3 100644 --- a/pype/plugins/tvpaint/load/load_reference_image.py +++ b/pype/plugins/tvpaint/load/load_reference_image.py @@ -162,6 +162,9 @@ class LoadImage(pipeline.Loader): """ # Create new containers first context = get_representation_context(representation) + # Change `fname` to new representation + self.fname = self.filepath_from_context(context) + name = container["name"] namespace = container["namespace"] new_container = self.load(context, name, namespace, {}) diff --git a/pype/scripts/otio_burnin.py b/pype/scripts/otio_burnin.py index 8da1dd8616..4f5c290e9d 100644 --- a/pype/scripts/otio_burnin.py +++ b/pype/scripts/otio_burnin.py @@ -5,7 +5,7 @@ import subprocess import platform import json import opentimelineio_contrib.adapters.ffmpeg_burnins as ffmpeg_burnins -from pype.api import config, resources +from pype.api import resources import pype.lib @@ -428,12 +428,6 @@ def burnins_from_data( } """ - # Use legacy processing when options are not set - if options is None or burnin_values is None: - presets = config.get_presets().get("tools", {}).get("burnins", {}) - options = presets.get("options") - burnin_values = presets.get("burnins") or {} - burnin = ModifiedBurnins(input_path, options_init=options) frame_start = data.get("frame_start") diff --git a/pype/scripts/slates/slate_base/lib.py b/pype/scripts/slates/slate_base/lib.py index 6b0c01883c..ec283e0e22 100644 --- a/pype/scripts/slates/slate_base/lib.py +++ b/pype/scripts/slates/slate_base/lib.py @@ -12,11 +12,6 @@ from .items import ( ItemTable, ItemImage, ItemRectangle, ItemPlaceHolder ) -try: - from pype.api.config import get_presets -except Exception: - get_presets = dict - log = logging.getLogger(__name__) @@ -41,11 +36,7 @@ def create_slates( ) elif slate_data is None: - slate_presets = ( - get_presets() - .get("tools", {}) - .get("slates") - ) or {} + slate_presets = {} slate_data = slate_presets.get(slate_name) if slate_data is None: raise ValueError( diff --git a/pype/settings/__init__.py b/pype/settings/__init__.py index 7a99ba0b2f..6f06b89460 100644 --- a/pype/settings/__init__.py +++ b/pype/settings/__init__.py @@ -1,11 +1,15 @@ from .lib import ( - system_settings, - project_settings, - environments + get_system_settings, + get_project_settings, + get_current_project_settings, + get_anatomy_settings, + get_environments ) __all__ = ( - "system_settings", - "project_settings", - "environments" + "get_system_settings", + "get_project_settings", + "get_current_project_settings", + "get_anatomy_settings", + "get_environments" ) diff --git a/pype/settings/defaults/environments/avalon.json b/pype/settings/defaults/environments/avalon.json deleted file mode 100644 index 832ba07e71..0000000000 --- a/pype/settings/defaults/environments/avalon.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "AVALON_CONFIG": "pype", - "AVALON_PROJECTS": "{PYPE_PROJECTS_PATH}", - "AVALON_USERNAME": "avalon", - "AVALON_PASSWORD": "secret", - "AVALON_DEBUG": "1", - "AVALON_MONGO": "mongodb://localhost:2707", - "AVALON_DB": "avalon", - "AVALON_DB_DATA": "{PYPE_SETUP_PATH}/../mongo_db_data", - "AVALON_EARLY_ADOPTER": "1", - "AVALON_SCHEMA": "{PYPE_MODULE_ROOT}/schema", - "AVALON_LOCATION": "http://127.0.0.1", - "AVALON_LABEL": "Pype", - "AVALON_TIMEOUT": "1000", - "AVALON_THUMBNAIL_ROOT": "" -} diff --git a/pype/settings/defaults/project_anatomy/attributes.json b/pype/settings/defaults/project_anatomy/attributes.json new file mode 100644 index 0000000000..8f35e41533 --- /dev/null +++ b/pype/settings/defaults/project_anatomy/attributes.json @@ -0,0 +1,29 @@ +{ + "fps": 25, + "frameStart": 1001, + "frameEnd": 1001, + "clipIn": 1, + "clipOut": 1, + "handleStart": 0, + "handleEnd": 0, + "resolutionWidth": 1920, + "resolutionHeight": 1080, + "pixelAspect": 1, + "applications": [], + "task_short_names": { + "Generic": "gener", + "Art": "art", + "Modeling": "mdl", + "Texture": "tex", + "Lookdev": "look", + "Rigging": "rig", + "Edit": "edit", + "Layout": "lay", + "Setdress": "dress", + "Animation": "anim", + "FX": "fx", + "Lighting": "lgt", + "Paint": "paint", + "Compositing": "comp" + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_anatomy/colorspace.json b/pype/settings/defaults/project_anatomy/colorspace.json deleted file mode 100644 index 8b934f810d..0000000000 --- a/pype/settings/defaults/project_anatomy/colorspace.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "nuke": { - "root": { - "colorManagement": "Nuke", - "OCIO_config": "nuke-default", - "defaultViewerLUT": "Nuke Root LUTs", - "monitorLut": "sRGB", - "int8Lut": "sRGB", - "int16Lut": "sRGB", - "logLut": "Cineon", - "floatLut": "linear" - }, - "viewer": { - "viewerProcess": "sRGB" - }, - "write": { - "render": { - "colorspace": "linear" - }, - "prerender": { - "colorspace": "linear" - }, - "still": { - "colorspace": "sRGB" - } - }, - "read": { - "[^-a-zA-Z0-9]beauty[^-a-zA-Z0-9]": "linear", - "[^-a-zA-Z0-9](P|N|Z|crypto)[^-a-zA-Z0-9]": "linear", - "[^-a-zA-Z0-9](plateRef)[^-a-zA-Z0-9]": "sRGB" - } - }, - "maya": { - - }, - "houdini": { - - }, - "resolve": { - - } -} diff --git a/pype/settings/defaults/project_anatomy/dataflow.json b/pype/settings/defaults/project_anatomy/dataflow.json deleted file mode 100644 index d2f470b5bc..0000000000 --- a/pype/settings/defaults/project_anatomy/dataflow.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "nuke": { - "nodes": { - "connected": true, - "modifymetadata": { - "_id": "connect_metadata", - "_previous": "ENDING", - "metadata.set.pype_studio_name": "{PYPE_STUDIO_NAME}", - "metadata.set.avalon_project_name": "{AVALON_PROJECT}", - "metadata.set.avalon_project_code": "{PYPE_STUDIO_CODE}", - "metadata.set.avalon_asset_name": "{AVALON_ASSET}" - }, - "crop": { - "_id": "connect_crop", - "_previous": "connect_metadata", - "box": [ - "{metadata.crop.x}", - "{metadata.crop.y}", - "{metadata.crop.right}", - "{metadata.crop.top}" - ] - }, - "write": { - "render": { - "_id": "output_write", - "_previous": "connect_crop", - "file_type": "exr", - "datatype": "16 bit half", - "compression": "Zip (1 scanline)", - "autocrop": true, - "tile_color": "0xff0000ff", - "channels": "rgb" - }, - "prerender": { - "_id": "output_write", - "_previous": "connect_crop", - "file_type": "exr", - "datatype": "16 bit half", - "compression": "Zip (1 scanline)", - "autocrop": false, - "tile_color": "0xc9892aff", - "channels": "rgba" - }, - "still": { - "_previous": "connect_crop", - "channels": "rgba", - "file_type": "tiff", - "datatype": "16 bit", - "compression": "LZW", - "tile_color": "0x4145afff" - } - } - } - } -} diff --git a/pype/settings/defaults/project_anatomy/imageio.json b/pype/settings/defaults/project_anatomy/imageio.json new file mode 100644 index 0000000000..4e98463ee4 --- /dev/null +++ b/pype/settings/defaults/project_anatomy/imageio.json @@ -0,0 +1,129 @@ +{ + "hiero": { + "workfile": { + "ocioConfigName": "nuke-default", + "ocioconfigpath": { + "windows": [], + "darwin": [], + "linux": [] + }, + "workingSpace": "linear", + "sixteenBitLut": "sRGB", + "eightBitLut": "sRGB", + "floatLut": "linear", + "logLut": "Cineon", + "viewerLut": "sRGB", + "thumbnailLut": "sRGB" + }, + "regexInputs": { + "inputs": [ + { + "regex": "[^-a-zA-Z0-9](plateRef).*(?=mp4)", + "colorspace": "sRGB" + } + ] + } + }, + "nuke": { + "workfile": { + "colorManagement": "Nuke", + "OCIO_config": "nuke-default", + "customOCIOConfigPath": { + "windows": [], + "darwin": [], + "linux": [] + }, + "workingSpaceLUT": "linear", + "monitorLut": "sRGB", + "int8Lut": "sRGB", + "int16Lut": "sRGB", + "logLut": "Cineon", + "floatLut": "linear" + }, + "nodes": { + "requiredNodes": [ + { + "plugins": [ + "CreateWriteRender" + ], + "nukeNodeClass": "Write", + "knobs": [ + { + "name": "file_type", + "value": "exr" + }, + { + "name": "datatype", + "value": "16 bit half" + }, + { + "name": "compression", + "value": "Zip (1 scanline)" + }, + { + "name": "autocrop", + "value": "True" + }, + { + "name": "tile_color", + "value": "0xff0000ff" + }, + { + "name": "channels", + "value": "rgb" + }, + { + "name": "colorspace", + "value": "linear" + } + ] + }, + { + "plugins": [ + "CreateWritePrerender" + ], + "nukeNodeClass": "Write", + "knobs": [ + { + "name": "file_type", + "value": "exr" + }, + { + "name": "datatype", + "value": "16 bit half" + }, + { + "name": "compression", + "value": "Zip (1 scanline)" + }, + { + "name": "autocrop", + "value": "False" + }, + { + "name": "tile_color", + "value": "0xff0000ff" + }, + { + "name": "channels", + "value": "rgb" + }, + { + "name": "colorspace", + "value": "linear" + } + ] + } + ], + "customNodes": [] + }, + "regexInputs": { + "inputs": [ + { + "regex": "[^-a-zA-Z0-9]beauty[^-a-zA-Z0-9]", + "colorspace": "linear" + } + ] + } + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_anatomy/templates.json b/pype/settings/defaults/project_anatomy/templates.json index 0fff0265b3..32f962556f 100644 --- a/pype/settings/defaults/project_anatomy/templates.json +++ b/pype/settings/defaults/project_anatomy/templates.json @@ -13,9 +13,6 @@ "file": "{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{representation}", "path": "{@folder}/{@file}" }, - "texture": { - "path": "{root}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}" - }, "publish": { "folder": "{root}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}", "file": "{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{representation}", @@ -26,5 +23,7 @@ "folder": "{root}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/master", "file": "{project[code]}_{asset}_{subset}_master<_{output}><.{frame}>.{representation}", "path": "{@folder}/{@file}" - } + }, + "delivery": {}, + "other": {} } \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/celaction.json b/pype/settings/defaults/project_settings/celaction.json new file mode 100644 index 0000000000..a4a321fb27 --- /dev/null +++ b/pype/settings/defaults/project_settings/celaction.json @@ -0,0 +1,13 @@ +{ + "publish": { + "ExtractCelactionDeadline": { + "enabled": true, + "deadline_department": "", + "deadline_priority": 50, + "deadline_pool": "", + "deadline_pool_secondary": "", + "deadline_group": "", + "deadline_chunk_size": 10 + } + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/ftrack.json b/pype/settings/defaults/project_settings/ftrack.json new file mode 100644 index 0000000000..023b85cb3b --- /dev/null +++ b/pype/settings/defaults/project_settings/ftrack.json @@ -0,0 +1,98 @@ +{ + "ftrack_actions_path": [], + "ftrack_events_path": [], + "events": { + "sync_to_avalon": { + "enabled": true, + "statuses_name_change": [ + "ready", + "not ready" + ] + }, + "push_frame_values_to_task": { + "enabled": true, + "interest_entity_types": [ + "shot", + "asset build" + ], + "interest_attributess": [ + "frameStart", + "frameEnd" + ] + }, + "thumbnail_updates": { + "enabled": true, + "levels": 2 + }, + "user_assignment": { + "enabled": true + }, + "status_update": { + "enabled": true, + "mapping": { + "In Progress": [ + "__any__" + ], + "Ready": [ + "Not Ready" + ], + "__ignore__": [ + "in prgoress", + "omitted", + "on hold" + ] + } + }, + "status_task_to_parent": { + "enabled": true, + "parent_status_match_all_task_statuses": { + "Completed": [ + "Approved", + "Omitted" + ] + }, + "parent_status_by_task_status": { + "In Progress": [ + "in progress", + "change requested", + "retake", + "pending review" + ] + } + }, + "status_task_to_version": { + "enabled": true, + "mapping": { + "Approved": [ + "Complete" + ] + } + }, + "status_version_to_task": { + "enabled": true, + "mapping": { + "Complete": [ + "Approved", + "Complete" + ] + } + }, + "first_version_status": { + "enabled": true, + "status": "" + }, + "next_task_update": { + "enabled": true, + "mapping": { + "Ready": "Not Ready" + } + } + }, + "publish": { + "IntegrateFtrackNote": { + "enabled": true, + "note_with_intent_template": "", + "note_labels": [] + } + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/ftrack/ftrack_config.json b/pype/settings/defaults/project_settings/ftrack/ftrack_config.json deleted file mode 100644 index 1ef3a9d69f..0000000000 --- a/pype/settings/defaults/project_settings/ftrack/ftrack_config.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "sync_to_avalon": { - "statuses_name_change": ["not ready", "ready"] - }, - - "status_update": { - "_ignore_": ["in progress", "ommited", "on hold"], - "Ready": ["not ready"], - "In Progress" : ["_any_"] - }, - "status_version_to_task": { - "__description__": "Status `from` (key) must be lowered!", - "in progress": "in progress", - "approved": "approved" - } -} diff --git a/pype/settings/defaults/project_settings/ftrack/ftrack_custom_attributes.json b/pype/settings/defaults/project_settings/ftrack/ftrack_custom_attributes.json deleted file mode 100644 index f03d473cd0..0000000000 --- a/pype/settings/defaults/project_settings/ftrack/ftrack_custom_attributes.json +++ /dev/null @@ -1,165 +0,0 @@ -[{ - "label": "FPS", - "key": "fps", - "type": "number", - "is_hierarchical": true, - "group": "avalon", - "write_security_role": ["ALL"], - "read_security_role": ["ALL"], - "default": null, - "config": { - "isdecimal": true - } -}, { - "label": "Applications", - "key": "applications", - "type": "enumerator", - "entity_type": "show", - "group": "avalon", - "config": { - "multiselect": true, - "data": [ - {"blender_2.80": "Blender 2.80"}, - {"blender_2.81": "Blender 2.81"}, - {"blender_2.82": "Blender 2.82"}, - {"blender_2.83": "Blender 2.83"}, - {"celaction_local": "CelAction2D Local"}, - {"maya_2017": "Maya 2017"}, - {"maya_2018": "Maya 2018"}, - {"maya_2019": "Maya 2019"}, - {"nuke_10.0": "Nuke 10.0"}, - {"nuke_11.2": "Nuke 11.2"}, - {"nuke_11.3": "Nuke 11.3"}, - {"nuke_12.0": "Nuke 12.0"}, - {"nukex_10.0": "NukeX 10.0"}, - {"nukex_11.2": "NukeX 11.2"}, - {"nukex_11.3": "NukeX 11.3"}, - {"nukex_12.0": "NukeX 12.0"}, - {"nukestudio_10.0": "NukeStudio 10.0"}, - {"nukestudio_11.2": "NukeStudio 11.2"}, - {"nukestudio_11.3": "NukeStudio 11.3"}, - {"nukestudio_12.0": "NukeStudio 12.0"}, - {"harmony_17": "Harmony 17"}, - {"houdini_16.5": "Houdini 16.5"}, - {"houdini_17": "Houdini 17"}, - {"houdini_18": "Houdini 18"}, - {"photoshop_2020": "Photoshop 2020"}, - {"python_3": "Python 3"}, - {"python_2": "Python 2"}, - {"premiere_2019": "Premiere Pro 2019"}, - {"premiere_2020": "Premiere Pro 2020"}, - {"resolve_16": "BM DaVinci Resolve 16"} - ] - } -}, { - "label": "Avalon auto-sync", - "key": "avalon_auto_sync", - "type": "boolean", - "entity_type": "show", - "group": "avalon", - "write_security_role": ["API", "Administrator"], - "read_security_role": ["API", "Administrator"] -}, { - "label": "Intent", - "key": "intent", - "type": "enumerator", - "entity_type": "assetversion", - "group": "avalon", - "config": { - "multiselect": false, - "data": [ - {"test": "Test"}, - {"wip": "WIP"}, - {"final": "Final"} - ] - } -}, { - "label": "Library Project", - "key": "library_project", - "type": "boolean", - "entity_type": "show", - "group": "avalon", - "write_security_role": ["API", "Administrator"], - "read_security_role": ["API", "Administrator"] -}, { - "label": "Clip in", - "key": "clipIn", - "type": "number", - "is_hierarchical": true, - "group": "avalon", - "default": null -}, { - "label": "Clip out", - "key": "clipOut", - "type": "number", - "is_hierarchical": true, - "group": "avalon", - "default": null -}, { - "label": "Frame start", - "key": "frameStart", - "type": "number", - "is_hierarchical": true, - "group": "avalon", - "default": null -}, { - "label": "Frame end", - "key": "frameEnd", - "type": "number", - "is_hierarchical": true, - "group": "avalon", - "default": null -}, { - "label": "Tools", - "key": "tools_env", - "type": "enumerator", - "is_hierarchical": true, - "group": "avalon", - "config": { - "multiselect": true, - "data": [ - {"mtoa_3.0.1": "mtoa_3.0.1"}, - {"mtoa_3.1.1": "mtoa_3.1.1"}, - {"mtoa_3.2.0": "mtoa_3.2.0"}, - {"yeti_2.1.2": "yeti_2.1"} - ] - } -}, { - "label": "Resolution Width", - "key": "resolutionWidth", - "type": "number", - "is_hierarchical": true, - "group": "avalon", - "default": null -}, { - "label": "Resolution Height", - "key": "resolutionHeight", - "type": "number", - "is_hierarchical": true, - "group": "avalon", - "default": null -}, { - "label": "Pixel aspect", - "key": "pixelAspect", - "type": "number", - "is_hierarchical": true, - "group": "avalon", - "config": { - "isdecimal": true - } -}, { - "label": "Frame handles start", - "key": "handleStart", - "type": "number", - "is_hierarchical": true, - "group": "avalon", - "default": null -}, { - "label": "Frame handles end", - "key": "handleEnd", - "type": "number", - "is_hierarchical": true, - "group": "avalon", - "default": null -} -] diff --git a/pype/settings/defaults/project_settings/ftrack/partnership_ftrack_cred.json b/pype/settings/defaults/project_settings/ftrack/partnership_ftrack_cred.json deleted file mode 100644 index 6b3a32f181..0000000000 --- a/pype/settings/defaults/project_settings/ftrack/partnership_ftrack_cred.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "server_url": "", - "api_key": "", - "api_user": "" -} diff --git a/pype/settings/defaults/project_settings/ftrack/plugins/server.json b/pype/settings/defaults/project_settings/ftrack/plugins/server.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/pype/settings/defaults/project_settings/ftrack/plugins/server.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/pype/settings/defaults/project_settings/ftrack/plugins/user.json b/pype/settings/defaults/project_settings/ftrack/plugins/user.json deleted file mode 100644 index 1ba8e9b511..0000000000 --- a/pype/settings/defaults/project_settings/ftrack/plugins/user.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "TestAction": { - "ignore_me": true - } -} diff --git a/pype/settings/defaults/project_settings/ftrack/project_defaults.json b/pype/settings/defaults/project_settings/ftrack/project_defaults.json deleted file mode 100644 index a4e3aa3362..0000000000 --- a/pype/settings/defaults/project_settings/ftrack/project_defaults.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "fps": 25, - "frameStart": 1001, - "frameEnd": 1100, - "clipIn": 1001, - "clipOut": 1100, - "handleStart": 10, - "handleEnd": 10, - - "resolutionHeight": 1080, - "resolutionWidth": 1920, - "pixelAspect": 1.0, - "applications": [ - "maya_2019", "nuke_11.3", "nukex_11.3", "nukestudio_11.3", "deadline" - ], - "tools_env": [], - "avalon_auto_sync": true -} diff --git a/pype/settings/defaults/project_settings/ftrack/project_schemas/default.json b/pype/settings/defaults/project_settings/ftrack/project_schemas/default.json new file mode 100644 index 0000000000..a90a0b3a8b --- /dev/null +++ b/pype/settings/defaults/project_settings/ftrack/project_schemas/default.json @@ -0,0 +1,39 @@ +{ + "object_types": ["Milestone", "Task", "Folder", "Asset Build", "Shot", "Library", "Sequence"], + "version_workflow": ["Pending Review", "Client Review", "On Farm", "Reviewed", "Render Complete", "Approved", "CBB", "Delivered", "Render Failed", "data"], + "task_workflow": ["Not Ready", "Ready", "Change Requested", "In progress", "Pending Review", "On Farm", "Waiting", "Render Complete", "Complete", "CBB", "On Hold", "Render Failed", "Omitted"], + "overrides": [{ + "task_types": ["Animation"], + "statuses": ["Not Ready", "Ready", "Change Requested", "Blocking", "Animating", "blocking review", "anim review", "Complete", "CBB", "On Hold", "Omitted"] + }, { + "task_types": ["Lighting"], + "statuses": ["Not Ready", "Ready", "Change Requested", "In progress", "To render", "On Farm", "Render Complete", "Complete", "CBB", "On Hold", "Render Failed", "Omitted"] + }], + "task_type_schema": ["Layout", "Animation", "Modeling", "Previz", "Lookdev", "FX", "Lighting", "Compositing", "Rigging", "Texture", "Matte-paint", "Roto-paint", "Art", "Match-moving", "Production", "Build", "Setdress", "Edit", "R&D", "Boards"], + "schemas": [{ + "object_type": "Shot", + "statuses": ["Omitted", "Normal", "Complete"], + "task_types": [] + }, { + "object_type": "Asset Build", + "statuses": ["Omitted", "Normal", "Complete"], + "task_types": ["Setups", "Sets", "Characters", "Props", "Locations", "Assembly", "R&D", "Elements"] + }, { + "object_type": "Milestone", + "statuses": ["Normal", "Complete"], + "task_types": ["Generic"] + }], + "task_templates": [{ + "name": "Character", + "task_types": ["Art", "Modeling", "Lookdev", "Rigging"] + }, { + "name": "Element", + "task_types": ["Modeling", "Lookdev"] + }, { + "name": "Prop", + "task_types": ["Modeling", "Lookdev", "Rigging"] + }, { + "name": "Location", + "task_types": ["Layout", "Setdress"] + }] +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/global.json b/pype/settings/defaults/project_settings/global.json new file mode 100644 index 0000000000..5f76f2d0f6 --- /dev/null +++ b/pype/settings/defaults/project_settings/global.json @@ -0,0 +1,182 @@ +{ + "publish": { + "IntegrateMasterVersion": { + "enabled": true + }, + "ExtractJpegEXR": { + "enabled": true, + "ffmpeg_args": { + "input": [], + "output": [] + } + }, + "ExtractReview": { + "enabled": true, + "profiles": [ + { + "families": [], + "hosts": [], + "outputs": { + "h264": { + "ext": "mp4", + "tags": [ + "burnin", + "ftrackreview" + ], + "ffmpeg_args": { + "video_filters": [], + "audio_filters": [], + "input": [ + "-gamma 2.2" + ], + "output": [ + "-pix_fmt yuv420p", + "-crf 18", + "-intra" + ] + }, + "filter": { + "families": [ + "render", + "review", + "ftrack" + ] + } + } + } + } + ] + }, + "ExtractBurnin": { + "enabled": true, + "options": { + "font_size": 42, + "opacity": 1, + "bg_opacity": 0, + "x_offset": 5, + "y_offset": 5, + "bg_padding": 5 + }, + "profiles": [ + { + "families": [], + "hosts": [], + "burnins": { + "burnin": { + "TOP_LEFT": "{yy}-{mm}-{dd}", + "TOP_CENTERED": "", + "TOP_RIGHT": "{anatomy[version]}", + "BOTTOM_LEFT": "{username}", + "BOTTOM_CENTERED": "{asset}", + "BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}" + } + } + } + ] + }, + "IntegrateAssetNew": { + "template_name_profiles": { + "publish": { + "families": [], + "tasks": [] + }, + "render": { + "families": [ + "review", + "render", + "prerender" + ] + } + } + }, + "ProcessSubmittedJobOnFarm": { + "enabled": true, + "deadline_department": "", + "deadline_pool": "", + "deadline_group": "", + "deadline_chunk_size": "", + "deadline_priority": "", + "aov_filter": { + "maya": [ + ".+(?:\\.|_)([Bb]eauty)(?:\\.|_).*" + ], + "nuke": [], + "aftereffects": [ + ".*" + ], + "celaction": [ + ".*" + ] + } + } + }, + "tools": { + "Creator": { + "families_smart_select": { + "Render": [ + "light", + "render" + ], + "Model": [ + "model" + ], + "Layout": [ + "layout" + ], + "Look": [ + "look" + ], + "Rig": [ + "rigging", + "rig" + ] + } + }, + "Workfiles": { + "last_workfile_on_startup": [ + { + "hosts": [], + "tasks": [], + "enabled": true + } + ], + "sw_folders": { + "compositing": [ + "nuke", + "ae" + ], + "modeling": [ + "maya", + "blender", + "zbrush" + ], + "lookdev": [ + "substance", + "textures" + ] + } + } + }, + "project_folder_structure": { + "__project_root__": { + "prod": {}, + "resources": { + "footage": { + "plates": {}, + "offline": {} + }, + "audio": {}, + "art_dept": {} + }, + "editorial": {}, + "assets[ftrack.Library]": { + "characters[ftrack]": {}, + "locations[ftrack]": {} + }, + "shots[ftrack.Sequence]": { + "scripts": {}, + "editorial[ftrack.Folder]": {} + } + } + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/global/creator.json b/pype/settings/defaults/project_settings/global/creator.json deleted file mode 100644 index d14e779f01..0000000000 --- a/pype/settings/defaults/project_settings/global/creator.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Model": ["model"], - "Render Globals": ["light", "render"], - "Layout": ["layout"], - "Set Dress": ["setdress"], - "Look": ["look"], - "Rig": ["rigging"] -} diff --git a/pype/settings/defaults/project_settings/global/project_folder_structure.json b/pype/settings/defaults/project_settings/global/project_folder_structure.json deleted file mode 100644 index 83bd5f12a9..0000000000 --- a/pype/settings/defaults/project_settings/global/project_folder_structure.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "__project_root__": { - "prod" : {}, - "resources" : { - "footage": { - "plates": {}, - "offline": {} - }, - "audio": {}, - "art_dept": {} - }, - "editorial" : {}, - "assets[ftrack.Library]": { - "characters[ftrack]": {}, - "locations[ftrack]": {} - }, - "shots[ftrack.Sequence]": { - "scripts": {}, - "editorial[ftrack.Folder]": {} - } - } -} diff --git a/pype/settings/defaults/project_settings/global/sw_folders.json b/pype/settings/defaults/project_settings/global/sw_folders.json deleted file mode 100644 index a154935dce..0000000000 --- a/pype/settings/defaults/project_settings/global/sw_folders.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "compositing": ["nuke", "ae"], - "modeling": ["maya", "app2"], - "lookdev": ["substance"], - "animation": [], - "lighting": [], - "rigging": [] -} diff --git a/pype/settings/defaults/project_settings/global/workfiles.json b/pype/settings/defaults/project_settings/global/workfiles.json deleted file mode 100644 index 393b2e3c10..0000000000 --- a/pype/settings/defaults/project_settings/global/workfiles.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "last_workfile_on_startup": [ - { - "enabled": false - } - ] -} diff --git a/pype/settings/defaults/project_settings/harmony.json b/pype/settings/defaults/project_settings/harmony.json new file mode 100644 index 0000000000..5eca4f60eb --- /dev/null +++ b/pype/settings/defaults/project_settings/harmony.json @@ -0,0 +1,7 @@ +{ + "publish": {}, + "general": { + "skip_resolution_check": false, + "skip_timelines_check": false + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/hiero.json b/pype/settings/defaults/project_settings/hiero.json new file mode 100644 index 0000000000..e4e65eedd3 --- /dev/null +++ b/pype/settings/defaults/project_settings/hiero.json @@ -0,0 +1,14 @@ +{ + "publish": { + "CollectInstanceVersion": { + "enabled": false + }, + "ExtractReviewCutUpVideo": { + "enabled": true, + "tags_addition": [ + "review" + ] + } + }, + "filters": {} +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/maya.json b/pype/settings/defaults/project_settings/maya.json new file mode 100644 index 0000000000..9193ea2b52 --- /dev/null +++ b/pype/settings/defaults/project_settings/maya.json @@ -0,0 +1,319 @@ +{ + "maya_capture": { + "Codec": { + "compression": "jpg", + "format": "image", + "quality": 95 + }, + "Display Options": { + "background": [ + 0.7, + 0.7, + 0.7 + ], + "backgroundBottom": [ + 0.7, + 0.7, + 0.7 + ], + "backgroundTop": [ + 0.7, + 0.7, + 0.7 + ], + "override_display": true + }, + "Generic": { + "isolate_view": true, + "off_screen": true + }, + "IO": { + "name": "", + "open_finished": true, + "raw_frame_numbers": true, + "recent_playblasts": [], + "save_file": true + }, + "PanZoom": { + "pan_zoom": true + }, + "Renderer": { + "rendererName": "vp2Renderer" + }, + "Resolution": { + "width": 1080, + "height": 1920, + "percent": 1.0, + "mode": "Custom" + }, + "Time Range": { + "start_frame": 0, + "end_frame": 0, + "frame": "", + "time": "Time Slider" + }, + "Viewport Options": { + "cameras": false, + "clipGhosts": false, + "controlVertices": false, + "deformers": false, + "dimensions": false, + "displayLights": 0, + "dynamicConstraints": false, + "dynamics": false, + "fluids": false, + "follicles": false, + "gpuCacheDisplayFilter": false, + "greasePencils": false, + "grid": false, + "hairSystems": true, + "handles": false, + "high_quality": true, + "hud": false, + "hulls": false, + "ikHandles": false, + "imagePlane": true, + "joints": false, + "lights": false, + "locators": false, + "manipulators": false, + "motionTrails": false, + "nCloths": false, + "nParticles": false, + "nRigids": false, + "nurbsCurves": false, + "nurbsSurfaces": false, + "override_viewport_options": true, + "particleInstancers": false, + "pivots": false, + "planes": false, + "pluginShapes": false, + "polymeshes": true, + "shadows": true, + "strokes": false, + "subdivSurfaces": false, + "textures": false, + "twoSidedLighting": true + }, + "Camera Options": { + "displayGateMask": false, + "displayResolution": false, + "displayFilmGate": false, + "displayFieldChart": false, + "displaySafeAction": false, + "displaySafeTitle": false, + "displayFilmPivot": false, + "displayFilmOrigin": false, + "overscan": 1.0 + } + }, + "publish": { + "CollectMayaRender": { + "sync_workfile_version": true + }, + "ValidateCameraAttributes": { + "enabled": true, + "optional": true + }, + "ValidateModelName": { + "enabled": true, + "material_file": { + "windows": "", + "darwin": "", + "linux": "" + }, + "regex": "" + }, + "ValidateAssemblyName": { + "enabled": true + }, + "ValidateShaderName": { + "enabled": true, + "regex": "" + }, + "ValidateMeshHasOverlappingUVs": { + "enabled": false + }, + "ExtractCameraAlembic": { + "enabled": true, + "optional": true, + "bake_attributes": [] + }, + "MayaSubmitDeadline": { + "enabled": true, + "tile_assembler_plugin": "DraftTileAssembler" + } + }, + "load": { + "colors": { + "model": [ + 0.821, + 0.518, + 0.117 + ], + "rig": [ + 0.144, + 0.443, + 0.463 + ], + "pointcache": [ + 0.368, + 0.821, + 0.117 + ], + "animation": [ + 0.368, + 0.821, + 0.117 + ], + "ass": [ + 1.0, + 0.332, + 0.312 + ], + "camera": [ + 0.447, + 0.312, + 1.0 + ], + "fbx": [ + 1.0, + 0.931, + 0.312 + ], + "mayaAscii": [ + 0.312, + 1.0, + 0.747 + ], + "setdress": [ + 0.312, + 1.0, + 0.747 + ], + "layout": [ + 0.312, + 1.0, + 0.747 + ], + "vdbcache": [ + 0.312, + 1.0, + 0.428 + ], + "vrayproxy": [ + 0.258, + 0.95, + 0.541 + ], + "yeticache": [ + 0.2, + 0.8, + 0.3 + ], + "yetiRig": [ + 0.0, + 0.8, + 0.5 + ] + } + }, + "workfile_build": { + "profiles": [ + { + "tasks": [ + "Lighting" + ], + "current_context": [ + { + "subset_name_filters": [ + "\".+[Mm]ain\"" + ], + "families": [ + "model" + ], + "repre_names": [ + "abc", + "ma" + ], + "loaders": [ + "ReferenceLoader" + ] + }, + { + "subset_name_filters": [], + "families": [ + "animation", + "pointcache" + ], + "repre_names": [ + "abc" + ], + "loaders": [ + "ReferenceLoader" + ] + }, + { + "subset_name_filters": [], + "families": [ + "rendersetup" + ], + "repre_names": [ + "json" + ], + "loaders": [ + "RenderSetupLoader" + ] + }, + { + "subset_name_filters": [], + "families": [ + "camera" + ], + "repre_names": [ + "abc" + ], + "loaders": [ + "ReferenceLoader" + ] + } + ], + "linked_assets": [ + { + "subset_name_filters": [], + "families": [ + "sedress" + ], + "repre_names": [ + "ma" + ], + "loaders": [ + "ReferenceLoader" + ] + }, + { + "subset_name_filters": [], + "families": [ + "ArnoldStandin" + ], + "repre_names": [ + "ass" + ], + "loaders": [ + "assLoader" + ] + } + ] + } + ] + }, + "filters": { + "preset 1": { + "ValidateNoAnimation": false, + "ValidateShapeDefaultNames": false + }, + "preset 2": { + "ValidateNoAnimation": false + } + } +} diff --git a/pype/settings/defaults/project_settings/maya/capture.json b/pype/settings/defaults/project_settings/maya/capture.json deleted file mode 100644 index b6c4893034..0000000000 --- a/pype/settings/defaults/project_settings/maya/capture.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "Codec": { - "compression": "jpg", - "format": "image", - "quality": 95 - }, - "Display Options": { - "background": [ - 0.7137254901960784, - 0.7137254901960784, - 0.7137254901960784 - ], - "backgroundBottom": [ - 0.7137254901960784, - 0.7137254901960784, - 0.7137254901960784 - ], - "backgroundTop": [ - 0.7137254901960784, - 0.7137254901960784, - 0.7137254901960784 - ], - "override_display": true - }, - "Generic": { - "isolate_view": true, - "off_screen": true - }, - "IO": { - "name": "", - "open_finished": false, - "raw_frame_numbers": false, - "recent_playblasts": [], - "save_file": false - }, - "PanZoom": { - "pan_zoom": true - }, - "Renderer": { - "rendererName": "vp2Renderer" - }, - "Resolution": { - "height": 1080, - "mode": "Custom", - "percent": 1.0, - "width": 1920 - }, - "Time Range": { - "end_frame": 25, - "frame": "", - "start_frame": 0, - "time": "Time Slider" - }, - "Viewport Options": { - "cameras": false, - "clipGhosts": false, - "controlVertices": false, - "deformers": false, - "dimensions": false, - "displayLights": 0, - "dynamicConstraints": false, - "dynamics": false, - "fluids": false, - "follicles": false, - "gpuCacheDisplayFilter": false, - "greasePencils": false, - "grid": false, - "hairSystems": false, - "handles": false, - "high_quality": true, - "hud": false, - "hulls": false, - "ikHandles": false, - "imagePlane": false, - "joints": false, - "lights": false, - "locators": false, - "manipulators": false, - "motionTrails": false, - "nCloths": false, - "nParticles": false, - "nRigids": false, - "nurbsCurves": false, - "nurbsSurfaces": false, - "override_viewport_options": true, - "particleInstancers": false, - "pivots": false, - "planes": false, - "pluginShapes": false, - "polymeshes": true, - "shadows": false, - "strokes": false, - "subdivSurfaces": false, - "textures": false, - "twoSidedLighting": true - }, - "Camera Options": { - "displayGateMask": false, - "displayResolution": false, - "displayFilmGate": false, - "displayFieldChart": false, - "displaySafeAction": false, - "displaySafeTitle": false, - "displayFilmPivot": false, - "displayFilmOrigin": false, - "overscan": 1.0 - } -} diff --git a/pype/settings/defaults/project_settings/muster/templates_mapping.json b/pype/settings/defaults/project_settings/muster/templates_mapping.json deleted file mode 100644 index 4edab9077d..0000000000 --- a/pype/settings/defaults/project_settings/muster/templates_mapping.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "3delight": 41, - "arnold": 46, - "arnold_sf": 57, - "gelato": 30, - "harware": 3, - "krakatoa": 51, - "file_layers": 7, - "mentalray": 2, - "mentalray_sf": 6, - "redshift": 55, - "renderman": 29, - "software": 1, - "software_sf": 5, - "turtle": 10, - "vector": 4, - "vray": 37, - "ffmpeg": 48 -} diff --git a/pype/settings/defaults/project_settings/nuke.json b/pype/settings/defaults/project_settings/nuke.json new file mode 100644 index 0000000000..873f249769 --- /dev/null +++ b/pype/settings/defaults/project_settings/nuke.json @@ -0,0 +1,90 @@ +{ + "create": { + "CreateWriteRender": { + "fpath_template": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}" + }, + "CreateWritePrerender": { + "fpath_template": "{work}/prerenders/nuke/{subset}/{subset}.{frame}.{ext}" + } + }, + "publish": { + "ExtractThumbnail": { + "enabled": true, + "nodes": { + "Reformat": [ + [ + "type", + "to format" + ], + [ + "format", + "HD_1080" + ], + [ + "filter", + "Lanczos6" + ], + [ + "black_outside", + true + ], + [ + "pbb", + false + ] + ] + } + }, + "ValidateNukeWriteKnobs": { + "enabled": true, + "knobs": { + "render": { + "review": true + } + } + }, + "ExtractReviewDataLut": { + "enabled": true + }, + "ExtractReviewDataMov": { + "enabled": true, + "viewer_lut_raw": false + }, + "ExtractSlateFrame": { + "viewer_lut_raw": false + }, + "NukeSubmitDeadline": { + "deadline_priority": 50, + "deadline_pool": "", + "deadline_pool_secondary": "", + "deadline_chunk_size": 1 + } + }, + "workfile_build": { + "profiles": [ + { + "tasks": [ + "compositing" + ], + "current_context": [ + { + "subset_name_filters": [], + "families": [ + "render", + "plate" + ], + "repre_names": [ + "exr", + "dpx" + ], + "loaders": [ + "LoadSequence" + ] + } + ], + "linked_assets": [] + } + ] + }, + "filters": {} +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/celaction/publish.json b/pype/settings/defaults/project_settings/plugins/celaction/publish.json deleted file mode 100644 index 4cda2d5656..0000000000 --- a/pype/settings/defaults/project_settings/plugins/celaction/publish.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "ExtractCelactionDeadline": { - "enabled": true, - "deadline_department": "", - "deadline_priority": 60, - "deadline_pool": "", - "deadline_pool_secondary": "", - "deadline_group": "", - "deadline_chunk_size": 10 - } -} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/config.json b/pype/settings/defaults/project_settings/plugins/config.json deleted file mode 100644 index 9e26dfeeb6..0000000000 --- a/pype/settings/defaults/project_settings/plugins/config.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/ftrack/publish.json b/pype/settings/defaults/project_settings/plugins/ftrack/publish.json deleted file mode 100644 index 8570a400e8..0000000000 --- a/pype/settings/defaults/project_settings/plugins/ftrack/publish.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "IntegrateFtrackNote": { - "enabled": true, - "note_with_intent_template": "{intent}: {comment}", - "note_labels": [] - } -} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/global/create.json b/pype/settings/defaults/project_settings/plugins/global/create.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/pype/settings/defaults/project_settings/plugins/global/create.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/pype/settings/defaults/project_settings/plugins/global/filter.json b/pype/settings/defaults/project_settings/plugins/global/filter.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/pype/settings/defaults/project_settings/plugins/global/filter.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/pype/settings/defaults/project_settings/plugins/global/load.json b/pype/settings/defaults/project_settings/plugins/global/load.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/pype/settings/defaults/project_settings/plugins/global/load.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/pype/settings/defaults/project_settings/plugins/global/publish.json b/pype/settings/defaults/project_settings/plugins/global/publish.json deleted file mode 100644 index 676985797f..0000000000 --- a/pype/settings/defaults/project_settings/plugins/global/publish.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "IntegrateMasterVersion": { - "enabled": false - }, - "ExtractJpegEXR": { - "enabled": true, - "ffmpeg_args": { - "input": [ - "-gamma 2.2" - ], - "output": [] - } - }, - "ExtractReview": { - "enabled": true, - "profiles": [ - { - "families": [], - "hosts": [], - "outputs": { - "h264": { - "ext": "mp4", - "tags": [ - "burnin", - "ftrackreview" - ], - "ffmpeg_args": { - "video_filters": [], - "audio_filters": [], - "input": [ - "-gamma 2.2" - ], - "output": [ - "-pix_fmt yuv420p", - "-crf 18", - "-intra" - ] - }, - "filter": { - "families": [ - "render", - "review", - "ftrack" - ] - } - } - } - } - ] - }, - "ExtractBurnin": { - "enabled": true, - "options": { - "font_size": 42, - "opacity": 1, - "bg_opacity": 0, - "x_offset": 5, - "y_offset": 5, - "bg_padding": 5 - }, - "profiles": [ - { - "burnins": { - "burnin": { - "TOP_LEFT": "{yy}-{mm}-{dd}", - "TOP_RIGHT": "{anatomy[version]}", - "TOP_CENTERED": "", - "BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}", - "BOTTOM_CENTERED": "{asset}", - "BOTTOM_LEFT": "{username}" - } - } - } - ] - }, - "IntegrateAssetNew": { - "template_name_profiles": { - "publish": { - "families": [], - "tasks": [] - }, - "render": { - "families": [ - "review", - "render", - "prerender" - ] - } - } - }, - "ProcessSubmittedJobOnFarm": { - "enabled": true, - "deadline_department": "", - "deadline_pool": "", - "deadline_group": "" - } -} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/maya/create.json b/pype/settings/defaults/project_settings/plugins/maya/create.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/pype/settings/defaults/project_settings/plugins/maya/create.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/pype/settings/defaults/project_settings/plugins/maya/filter.json b/pype/settings/defaults/project_settings/plugins/maya/filter.json deleted file mode 100644 index 83d6f05f31..0000000000 --- a/pype/settings/defaults/project_settings/plugins/maya/filter.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Preset n1": { - "ValidateNoAnimation": false, - "ValidateShapeDefaultNames": false - }, - "Preset n2": { - "ValidateNoAnimation": false - } -} diff --git a/pype/settings/defaults/project_settings/plugins/maya/load.json b/pype/settings/defaults/project_settings/plugins/maya/load.json deleted file mode 100644 index 260fbb35ee..0000000000 --- a/pype/settings/defaults/project_settings/plugins/maya/load.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "colors": { - "model": [0.821, 0.518, 0.117], - "rig": [0.144, 0.443, 0.463], - "pointcache": [0.368, 0.821, 0.117], - "animation": [0.368, 0.821, 0.117], - "ass": [1.0, 0.332, 0.312], - "camera": [0.447, 0.312, 1.0], - "fbx": [1.0, 0.931, 0.312], - "mayaAscii": [0.312, 1.0, 0.747], - "setdress": [0.312, 1.0, 0.747], - "layout": [0.312, 1.0, 0.747], - "vdbcache": [0.312, 1.0, 0.428], - "vrayproxy": [0.258, 0.95, 0.541], - "yeticache": [0.2, 0.8, 0.3], - "yetiRig": [0, 0.8, 0.5] - } -} diff --git a/pype/settings/defaults/project_settings/plugins/maya/maya/maya_capture.json b/pype/settings/defaults/project_settings/plugins/maya/maya/maya_capture.json deleted file mode 100644 index 02e6a9b95d..0000000000 --- a/pype/settings/defaults/project_settings/plugins/maya/maya/maya_capture.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "Codec": { - "compression": "jpg", - "format": "image", - "quality": 95 - }, - "Display Options": { - "background": [ - 0.714, - 0.714, - 0.714 - ], - "backgroundBottom": [ - 0.714, - 0.714, - 0.714 - ], - "backgroundTop": [ - 0.714, - 0.714, - 0.714 - ], - "override_display": true - }, - "Generic": { - "isolate_view": true, - "off_screen": true - }, - "IO": { - "name": "", - "open_finished": false, - "raw_frame_numbers": false, - "recent_playblasts": [], - "save_file": false - }, - "PanZoom": { - "pan_zoom": true - }, - "Renderer": { - "rendererName": "vp2Renderer" - }, - "Resolution": { - "width": 1920, - "height": 1080, - "percent": 1.0, - "mode": "Custom" - }, - "Time Range": { - "start_frame": 0, - "end_frame": 25, - "frame": "", - "time": "Time Slider" - }, - "Viewport Options": { - "cameras": false, - "clipGhosts": false, - "controlVertices": false, - "deformers": false, - "dimensions": false, - "displayLights": 0, - "dynamicConstraints": false, - "dynamics": false, - "fluids": false, - "follicles": false, - "gpuCacheDisplayFilter": false, - "greasePencils": false, - "grid": false, - "hairSystems": false, - "handles": false, - "high_quality": true, - "hud": false, - "hulls": false, - "ikHandles": false, - "imagePlane": false, - "joints": false, - "lights": false, - "locators": false, - "manipulators": false, - "motionTrails": false, - "nCloths": false, - "nParticles": false, - "nRigids": false, - "nurbsCurves": false, - "nurbsSurfaces": false, - "override_viewport_options": true, - "particleInstancers": false, - "pivots": false, - "planes": false, - "pluginShapes": false, - "polymeshes": true, - "shadows": false, - "strokes": false, - "subdivSurfaces": false, - "textures": false, - "twoSidedLighting": true - }, - "Camera Options": { - "displayGateMask": false, - "displayResolution": false, - "displayFilmGate": false, - "displayFieldChart": false, - "displaySafeAction": false, - "displaySafeTitle": false, - "displayFilmPivot": false, - "displayFilmOrigin": false, - "overscan": 1.0 - } -} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/maya/maya/publish.json b/pype/settings/defaults/project_settings/plugins/maya/maya/publish.json deleted file mode 100644 index 486f0917e2..0000000000 --- a/pype/settings/defaults/project_settings/plugins/maya/maya/publish.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "ValidateModelName": { - "enabled": true, - "material_file": { - "windows": "", - "darwin": "", - "linux": "" - }, - "regex": "" - }, - "ValidateAssemblyName": { - "enabled": true - }, - "ValidateShaderName": { - "enabled": true, - "regex": "(?P.*)_(.*)_SHD" - }, - "ValidateMeshHasOverlappingUVs": { - "enabled": true - } -} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/maya/maya/workfile_build.json b/pype/settings/defaults/project_settings/plugins/maya/maya/workfile_build.json deleted file mode 100644 index 9e26dfeeb6..0000000000 --- a/pype/settings/defaults/project_settings/plugins/maya/maya/workfile_build.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/maya/publish.json b/pype/settings/defaults/project_settings/plugins/maya/publish.json deleted file mode 100644 index 2b3637ff80..0000000000 --- a/pype/settings/defaults/project_settings/plugins/maya/publish.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "ValidateModelName": { - "enabled": false, - "material_file": "/path/to/shader_name_definition.txt", - "regex": "(.*)_(\\d)*_(?P.*)_(GEO)" - }, - "ValidateAssemblyName": { - "enabled": false - }, - "ValidateShaderName": { - "enabled": false, - "regex": "(?P.*)_(.*)_SHD" - }, - "ValidateMeshHasOverlappingUVs": { - "enabled": false - } -} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/maya/workfile_build.json b/pype/settings/defaults/project_settings/plugins/maya/workfile_build.json deleted file mode 100644 index 443bc2cb2c..0000000000 --- a/pype/settings/defaults/project_settings/plugins/maya/workfile_build.json +++ /dev/null @@ -1,136 +0,0 @@ -[ - { - "tasks": [ - "lighting" - ], - "current_context": [ - { - "subset_name_filters": [ - ".+[Mm]ain" - ], - "families": [ - "model" - ], - "repre_names": [ - "abc", - "ma" - ], - "loaders": [ - "ReferenceLoader" - ] - }, - { - "families": [ - "animation", - "pointcache" - ], - "repre_names": [ - "abc" - ], - "loaders": [ - "ReferenceLoader" - ] - }, - { - "families": [ - "rendersetup" - ], - "repre_names": [ - "json" - ], - "loaders": [ - "RenderSetupLoader" - ] - }, - { - "families": [ - "camera" - ], - "repre_names": [ - "abc" - ], - "loaders": [ - "ReferenceLoader" - ] - } - ], - "linked_assets": [ - { - "families": [ - "setdress" - ], - "repre_names": [ - "ma" - ], - "loaders": [ - "ReferenceLoader" - ] - }, - { - "families": [ - "ass" - ], - "repre_names": [ - "ass" - ], - "loaders": [ - "assLoader" - ] - } - ] - }, - { - "tasks": [ - "animation" - ], - "current_context": [ - { - "families": [ - "camera" - ], - "repre_names": [ - "abc", - "ma" - ], - "loaders": [ - "ReferenceLoader" - ] - }, - { - "families": [ - "audio" - ], - "repre_names": [ - "wav" - ], - "loaders": [ - "RenderSetupLoader" - ] - } - ], - "linked_assets": [ - { - "families": [ - "setdress" - ], - "repre_names": [ - "proxy" - ], - "loaders": [ - "ReferenceLoader" - ] - }, - { - "families": [ - "rig" - ], - "repre_names": [ - "ass" - ], - "loaders": [ - "rigLoader" - ] - } - ] - } -] \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/nuke/create.json b/pype/settings/defaults/project_settings/plugins/nuke/create.json deleted file mode 100644 index 79ab665696..0000000000 --- a/pype/settings/defaults/project_settings/plugins/nuke/create.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "CreateWriteRender": { - "fpath_template": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}" - }, - "CreateWritePrerender": { - "fpath_template": "{work}/prerenders/nuke/{subset}/{subset}.{frame}.{ext}" - } -} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/nuke/load.json b/pype/settings/defaults/project_settings/plugins/nuke/load.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/pype/settings/defaults/project_settings/plugins/nuke/load.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/pype/settings/defaults/project_settings/plugins/nuke/publish.json b/pype/settings/defaults/project_settings/plugins/nuke/publish.json deleted file mode 100644 index 50b5b27fc5..0000000000 --- a/pype/settings/defaults/project_settings/plugins/nuke/publish.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "ExtractThumbnail": { - "enabled": true, - "nodes": { - "Reformat": [ - [ - "type", - "to format" - ], - [ - "format", - "HD_1080" - ], - [ - "filter", - "Lanczos6" - ], - [ - "black_outside", - true - ], - [ - "pbb", - false - ] - ] - } - }, - "ValidateNukeWriteKnobs": { - "enabled": true, - "knobs": { - "render": { - "review": true - } - } - }, - "ExtractReviewDataLut": { - "enabled": true - }, - "ExtractReviewDataMov": { - "enabled": true, - "viewer_lut_raw": false - }, - "ExtractSlateFrame": { - "viewer_lut_raw": false - }, - "NukeSubmitDeadline": { - "deadline_priority": 50, - "deadline_pool": "", - "deadline_pool_secondary": "", - "deadline_chunk_size": 1 - } -} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/nuke/workfile_build.json b/pype/settings/defaults/project_settings/plugins/nuke/workfile_build.json deleted file mode 100644 index 4b48b46184..0000000000 --- a/pype/settings/defaults/project_settings/plugins/nuke/workfile_build.json +++ /dev/null @@ -1,23 +0,0 @@ -[ - { - "tasks": [ - "compositing" - ], - "current_context": [ - { - "families": [ - "render", - "plate" - ], - "repre_names": [ - "exr", - "dpx" - ], - "loaders": [ - "LoadSequence" - ] - } - ], - "linked_assets": [] - } -] \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/nukestudio/filter.json b/pype/settings/defaults/project_settings/plugins/nukestudio/filter.json deleted file mode 100644 index bd6a0dc1bd..0000000000 --- a/pype/settings/defaults/project_settings/plugins/nukestudio/filter.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "strict": { - "ValidateVersion": true, - "VersionUpWorkfile": true - }, - "benevolent": { - "ValidateVersion": false, - "VersionUpWorkfile": false - } -} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/nukestudio/publish.json b/pype/settings/defaults/project_settings/plugins/nukestudio/publish.json deleted file mode 100644 index d99a878c35..0000000000 --- a/pype/settings/defaults/project_settings/plugins/nukestudio/publish.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "CollectInstanceVersion": { - "enabled": false - }, - "ExtractReviewCutUpVideo": { - "enabled": true, - "tags_addition": [] - } -} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/resolve/create.json b/pype/settings/defaults/project_settings/plugins/resolve/create.json deleted file mode 100644 index 8ff5b15714..0000000000 --- a/pype/settings/defaults/project_settings/plugins/resolve/create.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "CreateShotClip": { - "clipName": "{track}{sequence}{shot}", - "folder": "takes", - "steps": 20 - } -} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/standalonepublisher/publish.json b/pype/settings/defaults/project_settings/plugins/standalonepublisher/publish.json deleted file mode 100644 index f7699ef9f7..0000000000 --- a/pype/settings/defaults/project_settings/plugins/standalonepublisher/publish.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "ExtractThumbnailSP": { - "ffmpeg_args": { - "input": [ - "-gamma 2.2" - ], - "output": [] - } - } -} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/test/create.json b/pype/settings/defaults/project_settings/plugins/test/create.json deleted file mode 100644 index fa0b2fc05f..0000000000 --- a/pype/settings/defaults/project_settings/plugins/test/create.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "MyTestCreator": { - "my_test_property": "B", - "active": false, - "new_property": "new", - "family": "new_family" - } -} diff --git a/pype/settings/defaults/project_settings/plugins/test/publish.json b/pype/settings/defaults/project_settings/plugins/test/publish.json deleted file mode 100644 index 3180dd5d8a..0000000000 --- a/pype/settings/defaults/project_settings/plugins/test/publish.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "MyTestPlugin": { - "label": "loaded from preset", - "optional": true, - "families": ["changed", "by", "preset"] - }, - "MyTestRemovedPlugin": { - "enabled": false - } -} diff --git a/pype/settings/defaults/project_settings/premiere/asset_default.json b/pype/settings/defaults/project_settings/premiere/asset_default.json deleted file mode 100644 index 84d2bde3d8..0000000000 --- a/pype/settings/defaults/project_settings/premiere/asset_default.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "frameStart": 1001, - "handleStart": 0, - "handleEnd": 0 -} diff --git a/pype/settings/defaults/project_settings/premiere/rules_tasks.json b/pype/settings/defaults/project_settings/premiere/rules_tasks.json deleted file mode 100644 index 333c9cd70b..0000000000 --- a/pype/settings/defaults/project_settings/premiere/rules_tasks.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "defaultTasks": ["Layout", "Animation"], - "taskToSubsets": { - "Layout": ["reference", "audio"], - "Animation": ["audio"] - }, - "subsetToRepresentations": { - "reference": { - "preset": "h264", - "representation": "mp4" - }, - "thumbnail": { - "preset": "jpeg_thumb", - "representation": "jpg" - }, - "audio": { - "preset": "48khz", - "representation": "wav" - } - } -} diff --git a/pype/settings/defaults/project_settings/resolve.json b/pype/settings/defaults/project_settings/resolve.json new file mode 100644 index 0000000000..cb7064ee76 --- /dev/null +++ b/pype/settings/defaults/project_settings/resolve.json @@ -0,0 +1,9 @@ +{ + "create": { + "CreateShotClip": { + "clipName": "{track}{sequence}{shot}", + "folder": "takes", + "steps": 20 + } + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/standalonepublisher.json b/pype/settings/defaults/project_settings/standalonepublisher.json new file mode 100644 index 0000000000..b8015f2832 --- /dev/null +++ b/pype/settings/defaults/project_settings/standalonepublisher.json @@ -0,0 +1,126 @@ +{ + "publish": { + "ExtractThumbnailSP": { + "ffmpeg_args": { + "input": [ + "gamma 2.2" + ], + "output": [] + } + } + }, + "create": { + "__dynamic_keys_labels__": { + "create_workfile": "Workfile", + "create_model": "Model", + "create_rig": "Rig", + "create_pointcache": "Pointcache", + "create_plate": "Plate", + "create_camera": "Camera", + "create_editorial": "Editorial", + "create_image": "Image", + "create_matchmove": "Matchmove", + "": "" + }, + "create_workfile": { + "name": "workfile", + "label": "Workfile", + "family": "workfile", + "icon": "cube", + "defaults": [ + "Main" + ], + "help": "Working scene backup" + }, + "create_model": { + "name": "model", + "label": "Model", + "family": "model", + "icon": "cube", + "defaults": [ + "Main" + ], + "help": "Polygonal static geometry" + }, + "create_rig": { + "name": "rig", + "label": "Rig", + "family": "rig", + "icon": "wheelchair", + "defaults": [ + "Main", + "Cloth" + ], + "help": "Artist-friendly rig with controls" + }, + "create_pointcache": { + "name": "pointcache", + "label": "Pointcache", + "family": "pointcache", + "icon": "gears", + "defaults": [ + "Main" + ], + "help": "Alembic pointcache for animated data" + }, + "create_plate": { + "name": "plate", + "label": "Plate", + "family": "plate", + "icon": "camera", + "defaults": [ + "Main", + "BG", + "Animatic", + "Reference", + "Offline" + ], + "help": "Footage for composting or reference" + }, + "create_camera": { + "name": "camera", + "label": "Camera", + "family": "camera", + "icon": "camera", + "defaults": [ + "Main" + ], + "help": "video-camera" + }, + "create_editorial": { + "name": "editorial", + "label": "Editorial", + "family": "editorial", + "icon": "image", + "defaults": [ + "Main" + ], + "help": "Editorial files to generate shots." + }, + "create_image": { + "name": "image", + "label": "Image file", + "family": "image", + "icon": "image", + "defaults": [ + "Reference", + "Texture", + "ConceptArt", + "MattePaint" + ], + "help": "Holder for all kinds of image data" + }, + "create_matchmove": { + "name": "matchmove", + "label": "Matchmove Scripts", + "family": "matchmove", + "icon": "empire", + "defaults": [ + "Camera", + "Object", + "Mocap" + ], + "help": "Script exported from matchmoving application" + } + } +} diff --git a/pype/settings/defaults/project_settings/standalonepublisher/families.json b/pype/settings/defaults/project_settings/standalonepublisher/families.json deleted file mode 100644 index d05941cc26..0000000000 --- a/pype/settings/defaults/project_settings/standalonepublisher/families.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "create_look": { - "name": "look", - "label": "Look", - "family": "look", - "icon": "paint-brush", - "defaults": ["Main"], - "help": "Shader connections defining shape look" - }, - "create_model": { - "name": "model", - "label": "Model", - "family": "model", - "icon": "cube", - "defaults": ["Main", "Proxy", "Sculpt"], - "help": "Polygonal static geometry" - }, - "create_workfile": { - "name": "workfile", - "label": "Workfile", - "family": "workfile", - "icon": "cube", - "defaults": ["Main"], - "help": "Working scene backup" - }, - "create_camera": { - "name": "camera", - "label": "Camera", - "family": "camera", - "icon": "video-camera", - "defaults": ["Main"], - "help": "Single baked camera" - }, - "create_pointcache": { - "name": "pointcache", - "label": "Pointcache", - "family": "pointcache", - "icon": "gears", - "defaults": ["Main"], - "help": "Alembic pointcache for animated data" - }, - "create_rig": { - "name": "rig", - "label": "Rig", - "family": "rig", - "icon": "wheelchair", - "defaults": ["Main"], - "help": "Artist-friendly rig with controls" - }, - "create_layout": { - "name": "layout", - "label": "Layout", - "family": "layout", - "icon": "cubes", - "defaults": ["Main"], - "help": "Simple scene for animators with camera" - }, - "create_plate": { - "name": "plate", - "label": "Plate", - "family": "plate", - "icon": "camera", - "defaults": ["Main", "BG", "Reference"], - "help": "Plates for compositors" - }, - "create_matchmove": { - "name": "matchmove", - "label": "Matchmove script", - "family": "matchmove", - "icon": "empire", - "defaults": ["Camera", "Object", "Mocap"], - "help": "Script exported from matchmoving application" - }, - "create_images": { - "name": "image", - "label": "Image file", - "family": "image", - "icon": "image", - "defaults": ["ConceptArt", "Reference", "Texture", "MattePaint"], - "help": "Holder for all kinds of image data" - }, - "create_editorial": { - "name": "editorial", - "label": "Editorial", - "family": "editorial", - "icon": "image", - "defaults": ["Main"], - "help": "Editorial files to generate shots." - } -} diff --git a/pype/settings/defaults/project_settings/tools/slates/example_HD.json b/pype/settings/defaults/project_settings/tools/slates/example_HD.json deleted file mode 100644 index b06391fb63..0000000000 --- a/pype/settings/defaults/project_settings/tools/slates/example_HD.json +++ /dev/null @@ -1,212 +0,0 @@ -{ - "width": 1920, - "height": 1080, - "destination_path": "{destination_path}", - "style": { - "*": { - "font-family": "arial", - "font-color": "#ffffff", - "font-bold": false, - "font-italic": false, - "bg-color": "#0077ff", - "alignment-horizontal": "left", - "alignment-vertical": "top" - }, - "layer": { - "padding": 0, - "margin": 0 - }, - "rectangle": { - "padding": 0, - "margin": 0, - "bg-color": "#E9324B", - "fill": true - }, - "main_frame": { - "padding": 0, - "margin": 0, - "bg-color": "#252525" - }, - "table": { - "padding": 0, - "margin": 0, - "bg-color": "transparent" - }, - "table-item": { - "padding": 5, - "padding-bottom": 10, - "margin": 0, - "bg-color": "#212121", - "bg-alter-color": "#272727", - "font-color": "#dcdcdc", - "font-bold": false, - "font-italic": false, - "alignment-horizontal": "left", - "alignment-vertical": "top", - "word-wrap": false, - "ellide": true, - "max-lines": 1 - }, - "table-item-col[0]": { - "font-size": 20, - "font-color": "#898989", - "font-bold": true, - "ellide": false, - "word-wrap": true, - "max-lines": null - }, - "table-item-col[1]": { - "font-size": 40, - "padding-left": 10 - }, - "#colorbar": { - "bg-color": "#9932CC" - } - }, - "items": [{ - "type": "layer", - "direction": 1, - "name": "MainLayer", - "style": { - "#MainLayer": { - "width": 1094, - "height": 1000, - "margin": 25, - "padding": 0 - }, - "#LeftSide": { - "margin-right": 25 - } - }, - "items": [{ - "type": "layer", - "name": "LeftSide", - "items": [{ - "type": "layer", - "direction": 1, - "style": { - "table-item": { - "bg-color": "transparent", - "padding-bottom": 20 - }, - "table-item-col[0]": { - "font-size": 20, - "font-color": "#898989", - "alignment-horizontal": "right" - }, - "table-item-col[1]": { - "alignment-horizontal": "left", - "font-bold": true, - "font-size": 40 - } - }, - "items": [{ - "type": "table", - "values": [ - ["Show:", "{project[name]}"] - ], - "style": { - "table-item-field[0:0]": { - "width": 150 - }, - "table-item-field[0:1]": { - "width": 580 - } - } - }, { - "type": "table", - "values": [ - ["Submitting For:", "{intent}"] - ], - "style": { - "table-item-field[0:0]": { - "width": 160 - }, - "table-item-field[0:1]": { - "width": 218, - "alignment-horizontal": "right" - } - } - }] - }, { - "type": "rectangle", - "style": { - "bg-color": "#bc1015", - "width": 1108, - "height": 5, - "fill": true - } - }, { - "type": "table", - "use_alternate_color": true, - "values": [ - ["Version name:", "{version_name}"], - ["Date:", "{date}"], - ["Shot Types:", "{shot_type}"], - ["Submission Note:", "{submission_note}"] - ], - "style": { - "table-item": { - "padding-bottom": 20 - }, - "table-item-field[0:1]": { - "font-bold": true - }, - "table-item-field[3:0]": { - "word-wrap": true, - "ellide": true, - "max-lines": 4 - }, - "table-item-col[0]": { - "alignment-horizontal": "right", - "width": 150 - }, - "table-item-col[1]": { - "alignment-horizontal": "left", - "width": 958 - } - } - }] - }, { - "type": "layer", - "name": "RightSide", - "items": [{ - "type": "placeholder", - "name": "thumbnail", - "path": "{thumbnail_path}", - "style": { - "width": 730, - "height": 412 - } - }, { - "type": "placeholder", - "name": "colorbar", - "path": "{color_bar_path}", - "return_data": true, - "style": { - "width": 730, - "height": 55 - } - }, { - "type": "table", - "use_alternate_color": true, - "values": [ - ["Vendor:", "{vendor}"], - ["Shot Name:", "{shot_name}"], - ["Frames:", "{frame_start} - {frame_end} ({duration})"] - ], - "style": { - "table-item-col[0]": { - "alignment-horizontal": "left", - "width": 200 - }, - "table-item-col[1]": { - "alignment-horizontal": "right", - "width": 530, - "font-size": 30 - } - } - }] - }] - }] -} diff --git a/pype/settings/defaults/project_settings/unreal.json b/pype/settings/defaults/project_settings/unreal.json new file mode 100644 index 0000000000..46b9ca2a18 --- /dev/null +++ b/pype/settings/defaults/project_settings/unreal.json @@ -0,0 +1,6 @@ +{ + "project_setup": { + "dev_mode": true, + "install_unreal_python_engine": false + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/unreal/project_setup.json b/pype/settings/defaults/project_settings/unreal/project_setup.json deleted file mode 100644 index 8a4dffc526..0000000000 --- a/pype/settings/defaults/project_settings/unreal/project_setup.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "dev_mode": false, - "install_unreal_python_engine": false -} diff --git a/pype/settings/defaults/system_settings/global/applications.json b/pype/settings/defaults/system_settings/applications.json similarity index 68% rename from pype/settings/defaults/system_settings/global/applications.json rename to pype/settings/defaults/system_settings/applications.json index 4bcea2fa30..208a8dc5e5 100644 --- a/pype/settings/defaults/system_settings/global/applications.json +++ b/pype/settings/defaults/system_settings/applications.json @@ -1,9 +1,9 @@ { "maya": { "enabled": true, - "label": "Autodesk Maya", + "label": "Maya", "icon": "{}/app_icons/maya.png", - "is_host": true, + "host_name": "maya", "environment": { "__environment_keys__": { "maya": [ @@ -39,11 +39,17 @@ "icon": "", "executables": { "windows": [ - "C:\\Program Files\\Autodesk\\Maya2020\\bin\\maya.exe" + [ + "C:\\Program Files\\Autodesk\\Maya2020\\bin\\maya.exe", + "" + ] ], "darwin": [], "linux": [ - "/usr/autodesk/maya2020/bin/maya" + [ + "/usr/autodesk/maya2020/bin/maya", + "" + ] ] }, "environment": { @@ -62,11 +68,17 @@ "icon": "", "executables": { "windows": [ - "C:\\Program Files\\Autodesk\\Maya2019\\bin\\maya.exe" + [ + "C:\\Program Files\\Autodesk\\Maya2019\\bin\\maya.exe", + "" + ] ], "darwin": [], "linux": [ - "/usr/autodesk/maya2019/bin/maya" + [ + "/usr/autodesk/maya2019/bin/maya", + "" + ] ] }, "environment": { @@ -85,11 +97,17 @@ "icon": "", "executables": { "windows": [ - "C:\\Program Files\\Autodesk\\Maya2017\\bin\\maya.exe" + [ + "C:\\Program Files\\Autodesk\\Maya2017\\bin\\maya.exe", + "" + ] ], "darwin": [], "linux": [ - "/usr/autodesk/maya2018/bin/maya" + [ + "/usr/autodesk/maya2018/bin/maya", + "" + ] ] }, "environment": { @@ -105,9 +123,9 @@ }, "mayabatch": { "enabled": true, - "label": "Autodesk MayaBatch", + "label": "MayaBatch", "icon": "{}/app_icons/maya.png", - "is_host": false, + "host_name": "maya", "environment": { "__environment_keys__": { "mayabatch": [ @@ -143,7 +161,10 @@ "icon": "", "executables": { "windows": [ - "C:\\Program Files\\Autodesk\\Maya2020\\bin\\mayabatch.exe" + [ + "C:\\Program Files\\Autodesk\\Maya2020\\bin\\mayabatch.exe", + "" + ] ], "darwin": [], "linux": [] @@ -164,7 +185,10 @@ "icon": "", "executables": { "windows": [ - "C:\\Program Files\\Autodesk\\Maya2019\\bin\\mayabatch.exe" + [ + "C:\\Program Files\\Autodesk\\Maya2019\\bin\\mayabatch.exe", + "" + ] ], "darwin": [], "linux": [] @@ -185,7 +209,10 @@ "icon": "", "executables": { "windows": [ - "C:\\Program Files\\Autodesk\\Maya2018\\bin\\mayabatch.exe" + [ + "C:\\Program Files\\Autodesk\\Maya2018\\bin\\mayabatch.exe", + "" + ] ], "darwin": [], "linux": [] @@ -205,7 +232,7 @@ "enabled": true, "label": "Nuke", "icon": "{}/app_icons/nuke.png", - "is_host": true, + "host_name": "nuke", "environment": { "__environment_keys__": { "nuke": [ @@ -223,6 +250,32 @@ } }, "variants": { + "nuke_12.2": { + "enabled": true, + "label": "", + "variant_label": "12.2", + "icon": "", + "executables": { + "windows": [ + [ + "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe", + "" + ] + ], + "darwin": [], + "linux": [ + [ + "/usr/local/Nuke12.2v3Nuke12.2", + "" + ] + ] + }, + "environment": { + "__environment_keys__": { + "nuke_12.2": [] + } + } + }, "nuke_12.0": { "enabled": true, "label": "", @@ -230,11 +283,17 @@ "icon": "", "executables": { "windows": [ - "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" + [ + "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe", + "" + ] ], "darwin": [], "linux": [ - "/usr/local/Nuke12.0v1/Nuke12.0" + [ + "/usr/local/Nuke12.0v1/Nuke12.0", + "" + ] ] }, "environment": { @@ -250,11 +309,17 @@ "icon": "", "executables": { "windows": [ - "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" + [ + "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe", + "" + ] ], "darwin": [], "linux": [ - "/usr/local/Nuke11.3v5/Nuke11.3" + [ + "/usr/local/Nuke11.3v5/Nuke11.3", + "" + ] ] }, "environment": { @@ -270,7 +335,10 @@ "icon": "", "executables": { "windows": [ - "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe" + [ + "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe", + "" + ] ], "darwin": [], "linux": [] @@ -287,7 +355,7 @@ "enabled": true, "label": "Nuke X", "icon": "{}/app_icons/nuke.png", - "is_host": true, + "host_name": "nuke", "environment": { "__environment_keys__": { "nukex": [ @@ -305,6 +373,32 @@ } }, "variants": { + "nukex_12.2": { + "enabled": true, + "label": "", + "variant_label": "12.2", + "icon": "", + "executables": { + "windows": [ + [ + "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe", + "--nukex" + ] + ], + "darwin": [], + "linux": [ + [ + "/usr/local/Nuke12.2v3Nuke12.2", + "--nukex" + ] + ] + }, + "environment": { + "__environment_keys__": { + "nukex_12.2": [] + } + } + }, "nukex_12.0": { "enabled": true, "label": "", @@ -312,11 +406,17 @@ "icon": "", "executables": { "windows": [ - "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" + [ + "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe", + "--nukex" + ] ], "darwin": [], "linux": [ - "/usr/local/Nuke12.0v1/Nuke12.0" + [ + "/usr/local/Nuke12.0v1/Nuke12.0", + "--nukex" + ] ] }, "environment": { @@ -332,11 +432,17 @@ "icon": "", "executables": { "windows": [ - "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" + [ + "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe", + "--nukex" + ] ], "darwin": [], "linux": [ - "/usr/local/Nuke11.3v5/Nuke11.3" + [ + "/usr/local/Nuke11.3v5/Nuke11.3", + "--nukex" + ] ] }, "environment": { @@ -352,7 +458,10 @@ "icon": "", "executables": { "windows": [ - "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe" + [ + "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe", + "--nukex" + ] ], "darwin": [], "linux": [] @@ -369,7 +478,7 @@ "enabled": true, "label": "Nuke Studio", "icon": "{}/app_icons/nuke.png", - "is_host": true, + "host_name": "hiero", "environment": { "__environment_keys__": { "nukestudio": [ @@ -391,6 +500,32 @@ "PYPE_LOG_NO_COLORS": "True" }, "variants": { + "nukestudio_12.2": { + "enabled": true, + "label": "", + "variant_label": "12.2", + "icon": "", + "executables": { + "windows": [ + [ + "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe", + "--studio" + ] + ], + "darwin": [], + "linux": [ + [ + "/usr/local/Nuke12.2v3Nuke12.2", + "--studio" + ] + ] + }, + "environment": { + "__environment_keys__": { + "nukestudio_12.2": [] + } + } + }, "nukestudio_12.0": { "enabled": true, "label": "", @@ -398,11 +533,17 @@ "icon": "", "executables": { "windows": [ - "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" + [ + "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe", + "--studio" + ] ], "darwin": [], "linux": [ - "/usr/local/Nuke12.0v1/Nuke12.0" + [ + "/usr/local/Nuke12.0v1/Nuke12.0", + "--studio" + ] ] }, "environment": { @@ -418,11 +559,17 @@ "icon": "", "executables": { "windows": [ - "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" + [ + "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe", + "--studio" + ] ], "darwin": [], "linux": [ - "/usr/local/Nuke11.3v5/Nuke11.3" + [ + "/usr/local/Nuke11.3v5/Nuke11.3", + "--studio" + ] ] }, "environment": { @@ -453,7 +600,7 @@ "enabled": true, "label": "Hiero", "icon": "{}/app_icons/hiero.png", - "is_host": true, + "host_name": "hiero", "environment": { "__environment_keys__": { "hiero": [ @@ -475,6 +622,32 @@ "PYPE_LOG_NO_COLORS": "True" }, "variants": { + "hiero_12.2": { + "enabled": true, + "label": "", + "variant_label": "12.2", + "icon": "", + "executables": { + "windows": [ + [ + "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe", + "--hiero" + ] + ], + "darwin": [], + "linux": [ + [ + "/usr/local/Nuke12.2v3Nuke12.2", + "--hiero" + ] + ] + }, + "environment": { + "__environment_keys__": { + "hiero_12.2": [] + } + } + }, "hiero_12.0": { "enabled": true, "label": "", @@ -482,11 +655,17 @@ "icon": "", "executables": { "windows": [ - "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" + [ + "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe", + "--hiero" + ] ], "darwin": [], "linux": [ - "/usr/local/Nuke12.0v1/Nuke12.0" + [ + "/usr/local/Nuke12.0v1/Nuke12.0", + "--hiero" + ] ] }, "environment": { @@ -502,11 +681,17 @@ "icon": "", "executables": { "windows": [ - "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" + [ + "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe", + "--hiero" + ] ], "darwin": [], "linux": [ - "/usr/local/Nuke11.3v5/Nuke11.3" + [ + "/usr/local/Nuke11.3v5/Nuke11.3", + "--hiero" + ] ] }, "environment": { @@ -522,7 +707,10 @@ "icon": "", "executables": { "windows": [ - "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe" + [ + "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe", + "--hiero" + ] ], "darwin": [], "linux": [] @@ -537,9 +725,9 @@ }, "fusion": { "enabled": true, - "label": "BlackMagic Fusion", + "label": "Fusion", "icon": "{}/app_icons/fusion.png", - "is_host": true, + "host_name": "fusion", "environment": { "__environment_keys__": { "fusion": [] @@ -582,9 +770,9 @@ }, "resolve": { "enabled": true, - "label": "Blackmagic DaVinci Resolve", + "label": "Resolve", "icon": "{}/app_icons/resolve.png", - "is_host": true, + "host_name": "resolve", "environment": { "__environment_keys__": { "resolve": [ @@ -660,9 +848,9 @@ }, "houdini": { "enabled": true, - "label": "SideFX Houdini", + "label": "Houdini", "icon": "{}/app_icons/houdini.png", - "is_host": true, + "host_name": "houdini", "environment": { "__environment_keys__": { "houdini": [ @@ -720,7 +908,7 @@ "enabled": true, "label": "Blender", "icon": "{}/app_icons/blender.png", - "is_host": true, + "host_name": "blender", "environment": { "__environment_keys__": { "blender": [ @@ -743,7 +931,12 @@ "variant_label": "2.90", "icon": "", "executables": { - "windows": [], + "windows": [ + [ + "C:\\Program Files\\Blender Foundation\\Blender 2.90\\blender.exe", + "" + ] + ], "darwin": [], "linux": [] }, @@ -759,7 +952,12 @@ "variant_label": "2.83", "icon": "", "executables": { - "windows": [], + "windows": [ + [ + "C:\\Program Files\\Blender Foundation\\Blender 2.83\\blender.exe", + "" + ] + ], "darwin": [], "linux": [] }, @@ -773,9 +971,9 @@ }, "harmony": { "enabled": true, - "label": "Toon Boom Harmony", + "label": "Harmony", "icon": "{}/app_icons/harmony.png", - "is_host": true, + "host_name": "harmony", "environment": { "__environment_keys__": { "harmony": [ @@ -803,38 +1001,6 @@ } } }, - "harmony_19": { - "enabled": true, - "label": "", - "variant_label": "19", - "icon": "", - "executables": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": { - "__environment_keys__": { - "harmony_19": [] - } - } - }, - "harmony_18": { - "enabled": true, - "label": "", - "variant_label": "18", - "icon": "", - "executables": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": { - "__environment_keys__": { - "harmony_18": [] - } - } - }, "harmony_17": { "enabled": true, "label": "", @@ -843,7 +1009,10 @@ "executables": { "windows": [], "darwin": [ - "/Applications/Toon Boom Harmony 17 Premium/Harmony Premium.app/Contents/MacOS/Harmony Premium" + [ + "/Applications/Toon Boom Harmony 17 Premium/Harmony Premium.app/Contents/MacOS/Harmony Premium", + "" + ] ], "linux": [] }, @@ -855,11 +1024,73 @@ } } }, + "tvpaint": { + "enabled": true, + "label": "TVPaint", + "icon": "{}/app_icons/tvpaint.png", + "host_name": "tvpaint", + "environment": { + "__environment_keys__": { + "tvpaint": [ + "PYPE_LOG_NO_COLORS" + ] + }, + "PYPE_LOG_NO_COLORS": "True" + }, + "variants": { + "tvpaint_Animation 11 (64bits)": { + "enabled": true, + "label": "", + "variant_label": "11 (64bits)", + "icon": "", + "executables": { + "windows": [ + [ + "C:\\Program Files\\TVPaint Developpement\\TVPaint Animation 11 (64bits)\\TVPaint Animation 11 (64bits).exe", + "" + ], + [ + "C:\\Program Files\\TVPaint Developpement\\TVPaint Animation 11 Pro (64bits) (DEMO)\\TVPaint Animation 11 Pro (64bits) (DEMO).exe", + "" + ] + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "tvpaint_Animation 11 (64bits)": [] + } + } + }, + "tvpaint_Animation 11 (32bits)": { + "enabled": true, + "label": "", + "variant_label": "11 (32bits)", + "icon": "", + "executables": { + "windows": [ + [ + "C:\\Program Files (x86)\\TVPaint Developpement\\TVPaint Animation 11 (32bits)\\TVPaint Animation 11 (32bits).exe", + "" + ] + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "tvpaint_Animation 11 (32bits)": [] + } + } + } + } + }, "photoshop": { "enabled": true, - "label": "Adobe Photoshop", + "label": "Photoshop", "icon": "{}/app_icons/photoshop.png", - "is_host": true, + "host_name": "photoshop", "environment": { "__environment_keys__": { "photoshop": [ @@ -883,7 +1114,12 @@ "variant_label": "2020", "icon": "", "executables": { - "windows": [], + "windows": [ + [ + "C:\\Program Files\\Adobe\\Adobe Photoshop 2020\\Photoshop.exe", + "" + ] + ], "darwin": [], "linux": [] }, @@ -892,6 +1128,93 @@ "photoshop_2020": [] } } + }, + "photoshop_2021": { + "enabled": true, + "label": "", + "variant_label": "2021", + "icon": "", + "executables": { + "windows": [ + [ + "C:\\Program Files\\Adobe\\Adobe Photoshop 2021\\Photoshop.exe", + "" + ] + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "photoshop_2021": [] + } + } + } + } + }, + "aftereffects": { + "enabled": true, + "label": "AfterEffects", + "icon": "{}/app_icons/aftereffects.png", + "host_name": "aftereffects", + "environment": { + "__environment_keys__": { + "aftereffects": [ + "AVALON_AFTEREFFECTS_WORKFILES_ON_LAUNCH", + "PYTHONPATH", + "PYPE_LOG_NO_COLORS", + "WEBSOCKET_URL", + "WORKFILES_SAVE_AS" + ] + }, + "AVALON_AFTEREFFECTS_WORKFILES_ON_LAUNCH": "1", + "PYTHONPATH": "{PYTHONPATH}", + "PYPE_LOG_NO_COLORS": "Yes", + "WEBSOCKET_URL": "ws://localhost:8097/ws/", + "WORKFILES_SAVE_AS": "Yes" + }, + "variants": { + "aftereffects_2020": { + "enabled": true, + "label": "", + "variant_label": "2020", + "icon": "", + "executables": { + "windows": [ + [ + "C:\\Program Files\\Adobe\\Adobe After Effects 2020\\Support Files\\AfterFX.exe", + "" + ] + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "aftereffects_2020": [] + } + } + }, + "aftereffects_2021": { + "enabled": true, + "label": "", + "variant_label": "2021", + "icon": "", + "executables": { + "windows": [ + [ + "C:\\Program Files\\Adobe\\Adobe After Effects 2021\\Support Files\\AfterFX.exe", + "" + ] + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "aftereffects_2021": [] + } + } } } }, @@ -899,7 +1222,7 @@ "enabled": true, "label": "CelAction 2D", "icon": "app_icons/celaction.png", - "is_host": true, + "host_name": "celaction", "environment": { "__environment_keys__": { "celaction": [ @@ -914,7 +1237,10 @@ "label": "", "variant_label": "Local", "icon": "{}/app_icons/celaction_local.png", - "executables": "", + "executables": [ + "", + "" + ], "environment": { "__environment_keys__": { "celation_Local": [] @@ -926,7 +1252,10 @@ "label": "", "variant_label": "Pulblish", "icon": "", - "executables": "", + "executables": [ + "", + "" + ], "environment": { "__environment_keys__": { "celation_Publish": [] @@ -939,7 +1268,7 @@ "enabled": true, "label": "Unreal Editor", "icon": "{}/app_icons/ue4.png'", - "is_host": true, + "host_name": "unreal", "environment": { "__environment_keys__": { "unreal": [ @@ -1033,7 +1362,7 @@ "enabled": true, "label": "DJV View", "icon": "{}/app_icons/djvView.png", - "is_host": false, + "host_name": "", "environment": { "__environment_keys__": { "djvview": [] diff --git a/pype/settings/defaults/system_settings/global/general.json b/pype/settings/defaults/system_settings/general.json similarity index 100% rename from pype/settings/defaults/system_settings/global/general.json rename to pype/settings/defaults/system_settings/general.json diff --git a/pype/settings/defaults/system_settings/global/hosts.json b/pype/settings/defaults/system_settings/global/hosts.json deleted file mode 100644 index 35ee708df3..0000000000 --- a/pype/settings/defaults/system_settings/global/hosts.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "blender_2.80": true, - "blender_2.81": true, - "blender_2.82": true, - "blender_2.83": true, - "celaction_local": true, - "celaction_remote": true, - "harmony_17": true, - "maya_2017": true, - "maya_2018": true, - "maya_2019": true, - "maya_2020": true, - "nuke_10.0": true, - "nuke_11.2": true, - "nuke_11.3": true, - "nuke_12.0": true, - "nukex_10.0": true, - "nukex_11.2": true, - "nukex_11.3": true, - "nukex_12.0": true, - "nukestudio_10.0": true, - "nukestudio_11.2": true, - "nukestudio_11.3": true, - "nukestudio_12.0": true, - "houdini_16": true, - "houdini_16.5": true, - "houdini_17": true, - "houdini_18": true, - "premiere_2019": true, - "premiere_2020": true, - "resolve_16": true, - "storyboardpro_7": true, - "unreal_4.24": true -} \ No newline at end of file diff --git a/pype/settings/defaults/system_settings/global/intent.json b/pype/settings/defaults/system_settings/global/intent.json deleted file mode 100644 index 844bd1b518..0000000000 --- a/pype/settings/defaults/system_settings/global/intent.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "items": { - "wip": "WIP", - "test": "TEST", - "final": "FINAL" - }, - "default": "wip" -} \ No newline at end of file diff --git a/pype/settings/defaults/system_settings/global/modules.json b/pype/settings/defaults/system_settings/global/modules.json deleted file mode 100644 index a28c2f4a03..0000000000 --- a/pype/settings/defaults/system_settings/global/modules.json +++ /dev/null @@ -1,124 +0,0 @@ -{ - "Avalon": { - "AVALON_MONGO": "mongodb://localhost:2707", - "AVALON_DB_DATA": "{PYPE_SETUP_PATH}/../mongo_db_data", - "AVALON_THUMBNAIL_ROOT": "{PYPE_SETUP_PATH}/../avalon_thumails", - "environment": { - "__environment_keys__": { - "avalon": [ - "AVALON_CONFIG", - "AVALON_PROJECTS", - "AVALON_SCHEMA", - "AVALON_LABEL", - "AVALON_TIMEOUT" - ] - }, - "AVALON_CONFIG": "pype", - "AVALON_PROJECTS": "{PYPE_PROJECTS_PATH}", - "AVALON_SCHEMA": "{PYPE_MODULE_ROOT}/schema", - "AVALON_LABEL": "Pype", - "AVALON_TIMEOUT": "1000" - } - }, - "Ftrack": { - "enabled": true, - "ftrack_server": "https://pype.ftrackapp.com", - "ftrack_actions_path": [], - "ftrack_events_path": [], - "FTRACK_EVENTS_MONGO_DB": "pype", - "FTRACK_EVENTS_MONGO_COL": "ftrack_events", - "sync_to_avalon": { - "statuses_name_change": [ - "ready", - "not ready" - ] - }, - "status_version_to_task": {}, - "status_update": { - "Ready": [ - "Not Ready" - ], - "In Progress": [ - "_any_" - ] - }, - "intent": { - "items": { - "-": "-", - "wip": "WIP", - "final": "Final", - "test": "Test" - }, - "default": "-" - }, - "environment": { - "__environment_keys__": { - "ftrack": [ - "FTRACK_ACTIONS_PATH", - "FTRACK_EVENTS_PATH", - "PYBLISHPLUGINPATH", - "PYTHONPATH" - ] - }, - "FTRACK_ACTIONS_PATH": [ - "{PYPE_MODULE_ROOT}/pype/modules/ftrack/actions" - ], - "FTRACK_EVENTS_PATH": [ - "{PYPE_MODULE_ROOT}/pype/modules/ftrack/events" - ], - "PYBLISHPLUGINPATH": [ - "{PYPE_MODULE_ROOT}/pype/plugins/ftrack/publish" - ], - "PYTHONPATH": [ - "{PYPE_MODULE_ROOT}/pype/vendor", - "{PYTHONPATH}" - ] - } - }, - "Rest Api": { - "default_port": 8021, - "exclude_ports": [] - }, - "Timers Manager": { - "enabled": true, - "full_time": 15.0, - "message_time": 0.5 - }, - "Clockify": { - "enabled": true, - "workspace_name": "studio name" - }, - "Deadline": { - "enabled": true, - "DEADLINE_REST_URL": "http://localhost:8082" - }, - "Muster": { - "enabled": false, - "MUSTER_REST_URL": "http://127.0.0.1:9890", - "templates_mapping": { - "file_layers": 7, - "mentalray": 2, - "mentalray_sf": 6, - "redshift": 55, - "renderman": 29, - "software": 1, - "software_sf": 5, - "turtle": 10, - "vector": 4, - "vray": 37, - "ffmpeg": 48 - } - }, - "Logging": { - "enabled": true - }, - "User setting": { - "enabled": true - }, - "Standalone Publish": { - "enabled": true - }, - "Idle Manager": { - "enabled": true - } -} \ No newline at end of file diff --git a/pype/settings/defaults/system_settings/modules.json b/pype/settings/defaults/system_settings/modules.json new file mode 100644 index 0000000000..a36a3b75cf --- /dev/null +++ b/pype/settings/defaults/system_settings/modules.json @@ -0,0 +1,205 @@ +{ + "Avalon": { + "AVALON_MONGO": "mongodb://localhost:2707", + "AVALON_DB_DATA": "{PYPE_SETUP_PATH}/../mongo_db_data", + "AVALON_THUMBNAIL_ROOT": "{PYPE_SETUP_PATH}/../avalon_thumails", + "environment": { + "__environment_keys__": { + "avalon": [ + "AVALON_CONFIG", + "AVALON_PROJECTS", + "AVALON_USERNAME", + "AVALON_PASSWORD", + "AVALON_DEBUG", + "AVALON_MONGO", + "AVALON_DB", + "AVALON_DB_DATA", + "AVALON_EARLY_ADOPTER", + "AVALON_SCHEMA", + "AVALON_LOCATION", + "AVALON_LABEL", + "AVALON_TIMEOUT", + "AVALON_THUMBNAIL_ROOT" + ] + }, + "AVALON_CONFIG": "pype", + "AVALON_PROJECTS": "{PYPE_PROJECTS_PATH}", + "AVALON_USERNAME": "avalon", + "AVALON_PASSWORD": "secret", + "AVALON_DEBUG": "1", + "AVALON_MONGO": "mongodb://localhost:2707", + "AVALON_DB": "avalon", + "AVALON_DB_DATA": "{PYPE_SETUP_PATH}/../mongo_db_data", + "AVALON_EARLY_ADOPTER": "1", + "AVALON_SCHEMA": "{PYPE_MODULE_ROOT}/schema", + "AVALON_LOCATION": "http://127.0.0.1", + "AVALON_LABEL": "Pype", + "AVALON_TIMEOUT": "1000", + "AVALON_THUMBNAIL_ROOT": "{PYPE_SETUP_PATH}/../avalon_thumails" + } + }, + "Ftrack": { + "enabled": true, + "ftrack_server": "https://pype.ftrackapp.com", + "ftrack_actions_path": [], + "ftrack_events_path": [], + "FTRACK_EVENTS_MONGO_DB": "pype", + "FTRACK_EVENTS_MONGO_COL": "ftrack_events", + "intent": { + "items": { + "-": "-", + "wip": "WIP", + "final": "Final", + "test": "Test" + }, + "default": "-" + }, + "custom_attributes": { + "show": { + "avalon_auto_sync": { + "default": "", + "write_security_role": [ + "API", + "Administrator" + ], + "read_security_role": [ + "API", + "Administrator" + ] + }, + "library_project": { + "default": "", + "write_security_role": [ + "API", + "Administrator" + ], + "read_security_role": [ + "API", + "Administrator" + ] + } + }, + "is_hierarchical": { + "fps": { + "default": "25", + "write_security_role": [], + "read_security_role": [] + }, + "frameStart": { + "default": "", + "write_security_role": [], + "read_security_role": [] + }, + "frameEnd": { + "default": "", + "write_security_role": [], + "read_security_role": [] + }, + "clipIn": { + "default": "", + "write_security_role": [], + "read_security_role": [] + }, + "clipOut": { + "default": "", + "write_security_role": [], + "read_security_role": [] + }, + "handleStart": { + "default": "", + "write_security_role": [], + "read_security_role": [] + }, + "handleEnd": { + "default": "", + "write_security_role": [], + "read_security_role": [] + }, + "resolutionWidth": { + "default": "", + "write_security_role": [], + "read_security_role": [] + }, + "resolutionHeight": { + "default": "", + "write_security_role": [], + "read_security_role": [] + }, + "pixelAspect": { + "default": "", + "write_security_role": [], + "read_security_role": [] + } + } + }, + "environment": { + "__environment_keys__": { + "ftrack": [ + "FTRACK_ACTIONS_PATH", + "FTRACK_EVENTS_PATH", + "PYBLISHPLUGINPATH", + "PYTHONPATH" + ] + }, + "FTRACK_ACTIONS_PATH": [ + "{PYPE_MODULE_ROOT}/pype/modules/ftrack/actions" + ], + "FTRACK_EVENTS_PATH": [ + "{PYPE_MODULE_ROOT}/pype/modules/ftrack/events" + ], + "PYBLISHPLUGINPATH": [ + "{PYPE_MODULE_ROOT}/pype/plugins/ftrack/publish" + ], + "PYTHONPATH": [ + "{PYPE_MODULE_ROOT}/pype/vendor", + "{PYTHONPATH}" + ] + } + }, + "Rest Api": { + "default_port": 8021, + "exclude_ports": [] + }, + "Timers Manager": { + "enabled": true, + "full_time": 15.0, + "message_time": 0.5 + }, + "Clockify": { + "enabled": false, + "workspace_name": "studio name" + }, + "Deadline": { + "enabled": true, + "DEADLINE_REST_URL": "http://localhost:8082" + }, + "Muster": { + "enabled": false, + "MUSTER_REST_URL": "http://127.0.0.1:9890", + "templates_mapping": { + "file_layers": 7, + "mentalray": 2, + "mentalray_sf": 6, + "redshift": 55, + "renderman": 29, + "software": 1, + "software_sf": 5, + "turtle": 10, + "vector": 4, + "vray": 37, + "ffmpeg": 48 + } + }, + "Logging": { + "enabled": true + }, + "User setting": { + "enabled": true + }, + "Standalone Publish": { + "enabled": true + }, + "Idle Manager": { + "enabled": true + } +} diff --git a/pype/settings/defaults/system_settings/standalone_publish/families.json b/pype/settings/defaults/system_settings/standalone_publish/families.json deleted file mode 100644 index d05941cc26..0000000000 --- a/pype/settings/defaults/system_settings/standalone_publish/families.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "create_look": { - "name": "look", - "label": "Look", - "family": "look", - "icon": "paint-brush", - "defaults": ["Main"], - "help": "Shader connections defining shape look" - }, - "create_model": { - "name": "model", - "label": "Model", - "family": "model", - "icon": "cube", - "defaults": ["Main", "Proxy", "Sculpt"], - "help": "Polygonal static geometry" - }, - "create_workfile": { - "name": "workfile", - "label": "Workfile", - "family": "workfile", - "icon": "cube", - "defaults": ["Main"], - "help": "Working scene backup" - }, - "create_camera": { - "name": "camera", - "label": "Camera", - "family": "camera", - "icon": "video-camera", - "defaults": ["Main"], - "help": "Single baked camera" - }, - "create_pointcache": { - "name": "pointcache", - "label": "Pointcache", - "family": "pointcache", - "icon": "gears", - "defaults": ["Main"], - "help": "Alembic pointcache for animated data" - }, - "create_rig": { - "name": "rig", - "label": "Rig", - "family": "rig", - "icon": "wheelchair", - "defaults": ["Main"], - "help": "Artist-friendly rig with controls" - }, - "create_layout": { - "name": "layout", - "label": "Layout", - "family": "layout", - "icon": "cubes", - "defaults": ["Main"], - "help": "Simple scene for animators with camera" - }, - "create_plate": { - "name": "plate", - "label": "Plate", - "family": "plate", - "icon": "camera", - "defaults": ["Main", "BG", "Reference"], - "help": "Plates for compositors" - }, - "create_matchmove": { - "name": "matchmove", - "label": "Matchmove script", - "family": "matchmove", - "icon": "empire", - "defaults": ["Camera", "Object", "Mocap"], - "help": "Script exported from matchmoving application" - }, - "create_images": { - "name": "image", - "label": "Image file", - "family": "image", - "icon": "image", - "defaults": ["ConceptArt", "Reference", "Texture", "MattePaint"], - "help": "Holder for all kinds of image data" - }, - "create_editorial": { - "name": "editorial", - "label": "Editorial", - "family": "editorial", - "icon": "image", - "defaults": ["Main"], - "help": "Editorial files to generate shots." - } -} diff --git a/pype/settings/defaults/system_settings/global/tools.json b/pype/settings/defaults/system_settings/tools.json similarity index 100% rename from pype/settings/defaults/system_settings/global/tools.json rename to pype/settings/defaults/system_settings/tools.json diff --git a/pype/settings/lib.py b/pype/settings/lib.py index c2ed6fcbca..d7395e7f1a 100644 --- a/pype/settings/lib.py +++ b/pype/settings/lib.py @@ -5,13 +5,25 @@ import copy log = logging.getLogger(__name__) +# Py2 + Py3 json decode exception +JSON_EXC = getattr(json.decoder, "JSONDecodeError", ValueError) + # Metadata keys for work with studio and project overrides M_OVERRIDEN_KEY = "__overriden_keys__" # Metadata key for storing information about environments M_ENVIRONMENT_KEY = "__environment_keys__" +# Metadata key for storing dynamic created labels +M_DYNAMIC_KEY_LABEL = "__dynamic_keys_labels__" # NOTE key popping not implemented yet M_POP_KEY = "__pop_key__" +METADATA_KEYS = ( + M_OVERRIDEN_KEY, + M_ENVIRONMENT_KEY, + M_DYNAMIC_KEY_LABEL, + M_POP_KEY +) + # Folder where studio overrides are stored STUDIO_OVERRIDES_PATH = os.getenv("PYPE_PROJECT_CONFIGS") or "" @@ -67,7 +79,7 @@ def reset_default_settings(): _DEFAULT_SETTINGS = None -def default_settings(): +def get_default_settings(): global _DEFAULT_SETTINGS if _DEFAULT_SETTINGS is None: _DEFAULT_SETTINGS = load_jsons_from_dir(DEFAULTS_DIR) @@ -80,7 +92,7 @@ def load_json_file(fpath): with open(fpath, "r") as opened_file: return json.load(opened_file) - except json.decoder.JSONDecodeError: + except JSON_EXC: log.warning( "File has invalid json format \"{}\"".format(fpath), exc_info=True @@ -145,7 +157,29 @@ def load_jsons_from_dir(path, *args, **kwargs): else: base_items = base_items_str.split(os.path.sep) + for filename in filenames: + basename, ext = os.path.splitext(filename) + if ext == ".json": + full_path = os.path.join(base, filename) + value = load_json_file(full_path) + dict_keys = base_items + [basename] + output = subkey_merge(output, value, dict_keys) + + for sub_key in sub_keys: + output = output[sub_key] + return output + + def find_environments(data, with_items=False, parents=None): + """ Find environemnt values from system settings by it's metadata. + + Args: + data(dict): System settings data or dictionary which may contain + environments metadata. + + Returns: + dict: Key as Environment key and value for `acre` module. + """ if not data or not isinstance(data, dict): return {} @@ -212,27 +246,6 @@ def subkey_merge(_dict, value, keys): return _dict -def studio_system_settings(): - """Studio overrides of system settings.""" - if os.path.exists(SYSTEM_SETTINGS_PATH): - return load_json_file(SYSTEM_SETTINGS_PATH) - return {} - - -def studio_project_settings(): - """Studio overrides of default project settings.""" - if os.path.exists(PROJECT_SETTINGS_PATH): - return load_json_file(PROJECT_SETTINGS_PATH) - return {} - - -def studio_project_anatomy(): - """Studio overrides of default project anatomy data.""" - if os.path.exists(PROJECT_ANATOMY_PATH): - return load_json_file(PROJECT_ANATOMY_PATH) - return {} - - def path_to_project_settings(project_name): if not project_name: return PROJECT_SETTINGS_PATH @@ -281,9 +294,9 @@ def save_project_settings(project_name, overrides): Do not use to store whole project settings data with defaults but only it's overrides with metadata defining how overrides should be applied in load - function. For loading should be used functions `studio_project_settings` - for global project settings and `project_settings_overrides` for - project specific settings. + function. For loading should be used function + `get_studio_project_settings_overrides` for global project settings + and `get_project_settings_overrides` for project specific settings. Args: project_name(str, null): Project name for which overrides are @@ -322,7 +335,28 @@ def save_project_anatomy(project_name, anatomy_data): json.dump(anatomy_data, file_stream, indent=4) -def project_settings_overrides(project_name): +def get_studio_system_settings_overrides(): + """Studio overrides of system settings.""" + if os.path.exists(SYSTEM_SETTINGS_PATH): + return load_json_file(SYSTEM_SETTINGS_PATH) + return {} + + +def get_studio_project_settings_overrides(): + """Studio overrides of default project settings.""" + if os.path.exists(PROJECT_SETTINGS_PATH): + return load_json_file(PROJECT_SETTINGS_PATH) + return {} + + +def get_studio_project_anatomy_overrides(): + """Studio overrides of default project anatomy data.""" + if os.path.exists(PROJECT_ANATOMY_PATH): + return load_json_file(PROJECT_ANATOMY_PATH) + return {} + + +def get_project_settings_overrides(project_name): """Studio overrides of project settings for specific project. Args: @@ -331,8 +365,6 @@ def project_settings_overrides(project_name): Returns: dict: Only overrides for entered project, may be empty dictionary. """ - if not project_name: - return {} path_to_json = path_to_project_settings(project_name) if not os.path.exists(path_to_json): @@ -340,7 +372,7 @@ def project_settings_overrides(project_name): return load_json_file(path_to_json) -def project_anatomy_overrides(project_name): +def get_project_anatomy_overrides(project_name): """Studio overrides of project anatomy for specific project. Args: @@ -388,31 +420,107 @@ def apply_overrides(source_data, override_data): return merge_overrides(_source_data, override_data) -def system_settings(): +def get_system_settings(clear_metadata=True): """System settings with applied studio overrides.""" - default_values = copy.deepcopy(default_settings()[SYSTEM_SETTINGS_KEY]) - studio_values = studio_system_settings() - return apply_overrides(default_values, studio_values) + default_values = get_default_settings()[SYSTEM_SETTINGS_KEY] + studio_values = get_studio_system_settings_overrides() + result = apply_overrides(default_values, studio_values) + if clear_metadata: + clear_metadata_from_settings(result) + return result -def project_settings(project_name): +def get_default_project_settings(clear_metadata=True): + """Project settings with applied studio's default project overrides.""" + default_values = get_default_settings()[PROJECT_SETTINGS_KEY] + studio_values = get_studio_project_settings_overrides() + result = apply_overrides(default_values, studio_values) + if clear_metadata: + clear_metadata_from_settings(result) + return result + + +def get_default_anatomy_settings(clear_metadata=True): + """Project anatomy data with applied studio's default project overrides.""" + default_values = get_default_settings()[PROJECT_ANATOMY_KEY] + studio_values = get_studio_project_anatomy_overrides() + result = apply_overrides(default_values, studio_values) + if clear_metadata: + clear_metadata_from_settings(result) + return result + + +def get_anatomy_settings(project_name, clear_metadata=True): + """Project anatomy data with applied studio and project overrides.""" + if not project_name: + raise ValueError( + "Must enter project name. Call " + "`get_default_anatomy_settings` to get project defaults." + ) + + studio_overrides = get_default_anatomy_settings() + project_overrides = get_project_anatomy_overrides(project_name) + + result = apply_overrides(studio_overrides, project_overrides) + if clear_metadata: + clear_metadata_from_settings(result) + return result + + +def get_project_settings(project_name, clear_metadata=True): """Project settings with applied studio and project overrides.""" - default_values = copy.deepcopy(default_settings()[PROJECT_SETTINGS_KEY]) - studio_values = studio_project_settings() + if not project_name: + raise ValueError( + "Must enter project name." + " Call `get_default_project_settings` to get project defaults." + ) - studio_overrides = apply_overrides(default_values, studio_values) + studio_overrides = get_default_project_settings(False) + project_overrides = get_project_settings_overrides(project_name) - project_overrides = project_settings_overrides(project_name) - - return apply_overrides(studio_overrides, project_overrides) + result = apply_overrides(studio_overrides, project_overrides) + if clear_metadata: + clear_metadata_from_settings(result) + return result -def environments(): - # TODO remove these defaults (All should be set with system settings) - envs = copy.deepcopy(default_settings()[ENVIRONMENTS_KEY]) - # This is part of loading environments from settings - envs_from_system_settings = find_environments(system_settings()) +def get_current_project_settings(): + """Project settings for current context project. - for env_group_key, values in envs_from_system_settings.items(): - envs[env_group_key] = values - return envs + Project name should be stored in environment variable `AVALON_PROJECT`. + This function should be used only in host context where environment + variable must be set and should not happen that any part of process will + change the value of the enviornment variable. + """ + project_name = os.environ.get("AVALON_PROJECT") + if not project_name: + raise ValueError( + "Missing context project in environemt variable `AVALON_PROJECT`." + ) + return get_project_settings(project_name) + + +def get_environments(): + """Calculated environment based on defaults and system settings. + + Any default environment also found in the system settings will be fully + overriden by the one from the system settings. + + Returns: + dict: Output should be ready for `acre` module. + """ + + return find_environments(get_system_settings(False)) + + +def clear_metadata_from_settings(values): + """Remove all metadata keys from loaded settings.""" + if isinstance(values, dict): + for key in tuple(values.keys()): + if key in METADATA_KEYS: + values.pop(key) + else: + clear_metadata_from_settings(values[key]) + elif isinstance(values, list): + for item in values: + clear_metadata_from_settings(item) diff --git a/pype/tests/test_lib_restructuralization.py b/pype/tests/test_lib_restructuralization.py index 152be8d1eb..957167a8bf 100644 --- a/pype/tests/test_lib_restructuralization.py +++ b/pype/tests/test_lib_restructuralization.py @@ -11,10 +11,6 @@ def test_backward_compatibility(printer): from pype.lib import get_latest_version from pype.lib import ApplicationLaunchFailed - from pype.lib import launch_application - from pype.lib import ApplicationAction - from pype.lib import get_avalon_database - from pype.lib import set_io_database from pype.lib import get_ffmpeg_tool_path from pype.lib import get_last_version_from_path diff --git a/pype/tools/assetcreator/app.py b/pype/tools/assetcreator/app.py index 71b1027ef4..f025af9662 100644 --- a/pype/tools/assetcreator/app.py +++ b/pype/tools/assetcreator/app.py @@ -6,7 +6,7 @@ try: import ftrack_api_old as ftrack_api except Exception: import ftrack_api -from pype.api import config +from pype.api import get_current_project_settings from pype import lib as pypelib from avalon.vendor.Qt import QtWidgets, QtCore from avalon import io, api, style, schema @@ -196,7 +196,7 @@ class Window(QtWidgets.QDialog): ft_project = session.query(project_query).one() schema_name = ft_project['project_schema']['name'] # Load config - schemas_items = config.get_presets().get('ftrack', {}).get( + schemas_items = get_current_project_settings().get('ftrack', {}).get( 'project_schemas', {} ) # Get info if it is silo project diff --git a/pype/tools/launcher/lib.py b/pype/tools/launcher/lib.py index f70929fc2e..7d2a49db9d 100644 --- a/pype/tools/launcher/lib.py +++ b/pype/tools/launcher/lib.py @@ -16,60 +16,13 @@ provides a bridge between the file-based project inventory and configuration. import os from Qt import QtGui -from avalon import lib from avalon.vendor import qtawesome from pype.api import resources -from pype.lib import ApplicationAction ICON_CACHE = {} NOT_FOUND = type("NotFound", (object, ), {}) -def get_application_actions(project): - """Define dynamic Application classes for project using `.toml` files - - Args: - project (dict): project document from the database - - Returns: - list: list of dictionaries - """ - - apps = [] - for app in project["config"]["apps"]: - try: - app_name = app["name"] - app_definition = lib.get_application(app_name) - except Exception as exc: - print("Unable to load application: %s - %s" % (app['name'], exc)) - continue - - # Get from app definition, if not there from app in project - icon = app_definition.get("icon", app.get("icon", "folder-o")) - color = app_definition.get("color", app.get("color", None)) - order = app_definition.get("order", app.get("order", 0)) - label = app_definition.get("label") or app.get("label") or app_name - label_variant = app_definition.get("label_variant") - group = app_definition.get("group") or app.get("group") - action = type( - "app_{}".format(app_name), - (ApplicationAction,), - { - "name": app_name, - "label": label, - "label_variant": label_variant, - "group": group, - "icon": icon, - "color": color, - "order": order, - "config": app_definition.copy() - } - ) - - apps.append(action) - return apps - - def get_action_icon(action): icon_name = action.icon if not icon_name: diff --git a/pype/tools/launcher/models.py b/pype/tools/launcher/models.py index 07db36fa9a..3e869f3e4a 100644 --- a/pype/tools/launcher/models.py +++ b/pype/tools/launcher/models.py @@ -7,7 +7,7 @@ from .actions import ApplicationAction from Qt import QtCore, QtGui from avalon.vendor import qtawesome from avalon import style, api -from pype.lib import ApplicationManager, env_value_to_bool +from pype.lib import ApplicationManager log = logging.getLogger(__name__) @@ -117,11 +117,7 @@ class ActionModel(QtGui.QStandardItemModel): super(ActionModel, self).__init__(parent=parent) self.dbcon = dbcon - self.use_manager = env_value_to_bool( - "PYPE_USE_APP_MANAGER", default=False - ) - if self.use_manager: - self.application_manager = ApplicationManager() + self.application_manager = ApplicationManager() self._session = {} self._groups = {} @@ -141,11 +137,7 @@ class ActionModel(QtGui.QStandardItemModel): actions = api.discover(api.Action) # Get available project actions and the application actions - if self.use_manager: - app_actions = self.get_application_actions() - else: - project_doc = self.dbcon.find_one({"type": "project"}) - app_actions = lib.get_application_actions(project_doc) + app_actions = self.get_application_actions() actions.extend(app_actions) self._registered_actions = actions diff --git a/pype/tools/pyblish_pype/control.py b/pype/tools/pyblish_pype/control.py index fecfffd821..4f7a43d6d1 100644 --- a/pype/tools/pyblish_pype/control.py +++ b/pype/tools/pyblish_pype/control.py @@ -22,7 +22,7 @@ import pyblish.version from . import util from .constants import InstanceStates -from pype.api import config +from pype.api import get_project_settings class IterationBreak(Exception): @@ -121,14 +121,14 @@ class Controller(QtCore.QObject): def presets_by_hosts(self): # Get global filters as base - presets = config.get_presets().get("plugins", {}) + presets = get_project_settings(os.environ['AVALON_PROJECT']) or {} if not presets: return {} - result = presets.get("global", {}).get("filter", {}) + result = presets.get("global", {}).get("filters", {}) hosts = pyblish.api.registered_hosts() for host in hosts: - host_presets = presets.get(host, {}).get("filter") + host_presets = presets.get(host, {}).get("filters") if not host_presets: continue diff --git a/pype/tools/pyblish_pype/model.py b/pype/tools/pyblish_pype/model.py index ec9689381e..88dce679f7 100644 --- a/pype/tools/pyblish_pype/model.py +++ b/pype/tools/pyblish_pype/model.py @@ -35,7 +35,7 @@ from six import text_type from .vendor import qtawesome from .constants import PluginStates, InstanceStates, GroupStates, Roles -from pype.api import config +from pype.api import get_system_settings # ItemTypes @@ -104,8 +104,9 @@ class IntentModel(QtGui.QStandardItemModel): self.default_index = 0 intents_preset = ( - config.get_presets() - .get("global", {}) + get_system_settings() + .get("modules", {}) + .get("Ftrack", {}) .get("intent", {}) ) diff --git a/pype/tools/settings/settings/README.md b/pype/tools/settings/settings/README.md index 4f4e9d305a..53f21aad06 100644 --- a/pype/tools/settings/settings/README.md +++ b/pype/tools/settings/settings/README.md @@ -116,43 +116,29 @@ ## Basic Dictionary inputs - these inputs wraps another inputs into {key: value} relation -### dict-invisible -- this input gives ability to wrap another inputs but keep them in same widget without visible divider - - this is for example used as first input widget -- has required keys `"key"` and `"children"` - - "children" says what children inputs are underneath - - "key" is key under which will be stored value from it's children -- output is dictionary `{the "key": children values}` -- can't have `"is_group"` key set to True as it breaks visual override showing -``` -{ - "type": "dict-invisible", - "key": "global", - "children": [ - ...ITEMS... - ] -} -``` - ## dict - this is another dictionary input wrapping more inputs but visually makes them different - item may be used as widget (in `list` or `dict-modifiable`) - in that case the only key modifier is `children` which is list of it's keys - USAGE: e.g. List of dictionaries where each dictionary have same structure. -- item options if is not used as widget - - required keys are `"key"` under which will be stored and `"label"` which will be shown in GUI - - this input can be expandable - - that can be set with key `"expandable"` as `True`/`False` (Default: `True`) - - with key `"expanded"` as `True`/`False` can be set that is expanded when GUI is opened (Default: `False`) - - it is possible to add darker background with `"highlight_content"` (Default: `False`) - - darker background has limits of maximum applies after 3-4 nested highlighted items there is not difference in the color +- item may be with or without `"label"` if is not used as widget + - required keys are `"key"` under which will be stored + - without label it is just wrap item holding `"key"` + - can't have `"is_group"` key set to True as it breaks visual override showing + - if `"label"` is entetered there which will be shown in GUI + - item with label can be collapsable + - that can be set with key `"collapsable"` as `True`/`False` (Default: `True`) + - with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`) + - it is possible to add darker background with `"highlight_content"` (Default: `False`) + - darker background has limits of maximum applies after 3-4 nested highlighted items there is not difference in the color + - output is dictionary `{the "key": children values}` ``` # Example { "key": "applications", "type": "dict", "label": "Applications", - "expandable": true, + "collapsable": true, "highlight_content": true, "is_group": true, "is_file": true, @@ -161,13 +147,22 @@ ] } +# Without label +{ + "type": "dict", + "key": "global", + "children": [ + ...ITEMS... + ] +} + # When used as widget { "type": "list", "key": "profiles", "label": "Profiles", "object_type": { - "type": "dict-item", + "type": "dict", "children": [ { "key": "families", @@ -315,12 +310,15 @@ - items in this input can be removed and added same way as in `list` input - value items in dictionary must be the same type - type of items is defined with key `"object_type"` +- required keys may be defined under `"required_keys"` + - required keys must be defined as a list (e.g. `["key_1"]`) and are moved to the top + - these keys can't be removed or edited (it is possible to edit label if item is collapsible) - there are 2 possible ways how to set the type: 1.) dictionary with item modifiers (`number` input has `minimum`, `maximum` and `decimals`) in that case item type must be set as value of `"type"` (example below) 2.) item type name as string without modifiers (e.g. `text`) -- this input can be expandable - - that can be set with key `"expandable"` as `True`/`False` (Default: `True`) - - with key `"expanded"` as `True`/`False` can be set that is expanded when GUI is opened (Default: `False`) +- this input can be collapsable + - that can be set with key `"collapsable"` as `True`/`False` (Default: `True`) + - with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`) 1.) with item modifiers ``` @@ -441,11 +439,9 @@ ## Proxy wrappers - should wraps multiple inputs only visually - these does not have `"key"` key and do not allow to have `"is_file"` or `"is_group"` modifiers enabled +- can't be used as widget (first item in e.g. `list`, `dict-modifiable`, etc.) ### form -- DEPRECATED - - may be used only in `dict` and `dict-invisible` where is currently used grid layout so form is not needed - - item is kept as still may be used in specific cases - wraps inputs into form look layout - should be used only for Pure inputs @@ -467,3 +463,24 @@ ] } ``` + + +### collapsible-wrap +- wraps inputs into collapsible widget + - looks like `dict` but does not hold `"key"` +- should be used only for Pure inputs + +``` +{ + "type": "collapsible-wrap", + "label": "Collapsible example" + "children": [ + { + "type": "text", + "key": "_example_input_collapsible", + "label": "Example input in collapsible wrapper" + }, { + ... + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/0_project_gui_schema.json b/pype/tools/settings/settings/gui_schemas/projects_schema/0_project_gui_schema.json deleted file mode 100644 index cf95bf4c45..0000000000 --- a/pype/tools/settings/settings/gui_schemas/projects_schema/0_project_gui_schema.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "key": "project", - "type": "dict-invisible", - "children": [ - { - "type": "anatomy", - "key": "project_anatomy", - "children": [ - { - "type": "anatomy_roots", - "key": "roots", - "is_file": true - }, { - "type": "anatomy_templates", - "key": "templates", - "is_file": true - } - ] - }, { - "type": "dict-invisible", - "key": "project_settings", - "children": [ - { - "type": "schema", - "name": "1_plugins_gui_schema" - } - ] - } - ] -} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json b/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json deleted file mode 100644 index 87912cfdc0..0000000000 --- a/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json +++ /dev/null @@ -1,614 +0,0 @@ -{ - "type": "dict", - "collapsable": true, - "key": "plugins", - "label": "Plugins", - "children": [{ - "type": "dict", - "collapsable": true, - "key": "celaction", - "label": "CelAction", - "children": [{ - "type": "dict", - "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [{ - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ExtractCelactionDeadline", - "label": "ExtractCelactionDeadline", - "is_group": true, - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "text", - "key": "deadline_department", - "label": "Deadline apartment" - }, { - "type": "number", - "key": "deadline_priority", - "label": "Deadline priority" - }, { - "type": "text", - "key": "deadline_pool", - "label": "Deadline pool" - }, { - "type": "text", - "key": "deadline_pool_secondary", - "label": "Deadline pool (secondary)" - }, { - "type": "text", - "key": "deadline_group", - "label": "Deadline Group" - }, { - "type": "number", - "key": "deadline_chunk_size", - "label": "Deadline Chunk size" - }] - }] - }] - }, { - "type": "dict", - "collapsable": true, - "key": "ftrack", - "label": "Ftrack", - "children": [{ - "type": "dict", - "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [{ - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "IntegrateFtrackNote", - "label": "IntegrateFtrackNote", - "is_group": true, - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "text", - "key": "note_with_intent_template", - "label": "Note with intent template" - }, { - "type": "list", - "object_type": "text", - "key": "note_labels", - "label": "Note labels" - }] - }] - }] - }, { - "type": "dict", - "collapsable": true, - "key": "global", - "label": "Global", - "children": [{ - "type": "dict", - "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [{ - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "IntegrateMasterVersion", - "label": "IntegrateMasterVersion", - "is_group": true, - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }] - }, { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ExtractJpegEXR", - "label": "ExtractJpegEXR", - "is_group": true, - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "dict-invisible", - "key": "ffmpeg_args", - "children": [{ - "type": "list", - "object_type": "text", - "key": "input", - "label": "FFmpeg input arguments" - }, { - "type": "list", - "object_type": "text", - "key": "output", - "label": "FFmpeg output arguments" - }] - }] - }, { - "type": "dict", - "collapsable": true, - "key": "ExtractReview", - "label": "ExtractReview", - "checkbox_key": "enabled", - "is_group": true, - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "list", - "key": "profiles", - "label": "Profiles", - "object_type": { - "type": "dict", - "children": [{ - "key": "families", - "label": "Families", - "type": "list", - "object_type": "text" - }, { - "key": "hosts", - "label": "Hosts", - "type": "list", - "object_type": "text" - }, { - "type": "splitter" - }, { - "key": "outputs", - "label": "Output Definitions", - "type": "dict-modifiable", - "highlight_content": true, - "object_type": { - "type": "dict", - "children": [{ - "key": "ext", - "label": "Output extension", - "type": "text" - }, { - "key": "tags", - "label": "Tags", - "type": "enum", - "multiselection": true, - "enum_items": [{ - "burnin": "Add burnins" - }, - { - "ftrackreview": "Add to Ftrack" - }, - { - "delete": "Delete output" - }, - { - "slate-frame": "Add slate frame" - }, - { - "no-hnadles": "Skip handle frames" - } - ] - }, { - "key": "ffmpeg_args", - "label": "FFmpeg arguments", - "type": "dict", - "highlight_content": true, - "children": [{ - "key": "video_filters", - "label": "Video filters", - "type": "list", - "object_type": "text" - }, { - "type": "splitter" - }, { - "key": "audio_filters", - "label": "Audio filters", - "type": "list", - "object_type": "text" - }, { - "type": "splitter" - }, { - "key": "input", - "label": "Input arguments", - "type": "list", - "object_type": "text" - }, { - "type": "splitter" - }, { - "key": "output", - "label": "Output arguments", - "type": "list", - "object_type": "text" - }] - }, { - "key": "filter", - "label": "Additional output filtering", - "type": "dict", - "highlight_content": true, - "children": [{ - "key": "families", - "label": "Families", - "type": "list", - "object_type": "text" - }] - }] - } - }] - } - }] - }, { - "type": "dict", - "collapsable": true, - "key": "ExtractBurnin", - "label": "ExtractBurnin", - "checkbox_key": "enabled", - "is_group": true, - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "dict", - "collapsable": true, - "key": "options", - "label": "Burnin formating options", - "children": [{ - "type": "number", - "key": "font_size", - "label": "Font size" - }, { - "type": "number", - "key": "opacity", - "label": "Font opacity" - }, { - "type": "number", - "key": "bg_opacity", - "label": "Background opacity" - }, { - "type": "number", - "key": "x_offset", - "label": "X Offset" - }, { - "type": "number", - "key": "y_offset", - "label": "Y Offset" - }, { - "type": "number", - "key": "bg_padding", - "label": "Padding aroung text" - }] - }, { - "type": "raw-json", - "key": "profiles", - "label": "Burnin profiles" - }] - }, { - "type": "dict", - "collapsable": true, - "key": "IntegrateAssetNew", - "label": "IntegrateAssetNew", - "is_group": true, - "children": [{ - "type": "raw-json", - "key": "template_name_profiles", - "label": "template_name_profiles" - }] - }, { - "type": "dict", - "collapsable": true, - "key": "ProcessSubmittedJobOnFarm", - "label": "ProcessSubmittedJobOnFarm", - "checkbox_key": "enabled", - "is_group": true, - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "text", - "key": "deadline_department", - "label": "Deadline department" - }, { - "type": "text", - "key": "deadline_pool", - "label": "Deadline Pool" - }, { - "type": "text", - "key": "deadline_group", - "label": "Deadline Group" - }] - }] - }] - }, { - "type": "dict-invisible", - "collapsable": true, - "key": "maya", - "label": "Maya", - "children": [{ - "type": "dict", - "collapsable": true, - "key": "maya", - "label": "Maya", - "children": [ - - { - "type": "schema", - "name": "2_maya_capture" - }, - { - "type": "schema", - "name": "2_maya_plugins" - }, - { - "type": "schema", - "name": "2_maya_workfiles" - } - ] - }] - }, { - "type": "dict", - "collapsable": true, - "key": "nuke", - "label": "Nuke", - "children": [{ - "type": "dict", - "collapsable": true, - "key": "create", - "label": "Create plugins", - "is_file": true, - "children": [{ - "type": "dict", - "collapsable": false, - "key": "CreateWriteRender", - "label": "CreateWriteRender", - "is_group": true, - "children": [{ - "type": "text", - "key": "fpath_template", - "label": "Path template" - }] - }, { - "type": "dict", - "collapsable": false, - "key": "CreateWritePrerender", - "label": "CreateWritePrerender", - "is_group": true, - "children": [{ - "type": "text", - "key": "fpath_template", - "label": "Path template" - }] - }] - }, { - "type": "dict", - "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [{ - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ExtractThumbnail", - "label": "ExtractThumbnail", - "is_group": true, - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "raw-json", - "key": "nodes", - "label": "Nodes" - }] - }, { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ValidateNukeWriteKnobs", - "label": "ValidateNukeWriteKnobs", - "is_group": true, - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "raw-json", - "key": "knobs", - "label": "Knobs" - }] - }, { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ExtractReviewDataLut", - "label": "ExtractReviewDataLut", - "is_group": true, - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }] - }, { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ExtractReviewDataMov", - "label": "ExtractReviewDataMov", - "is_group": true, - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "boolean", - "key": "viewer_lut_raw", - "label": "Viewer LUT raw" - }] - }, { - "type": "dict", - "collapsable": true, - "key": "ExtractSlateFrame", - "label": "ExtractSlateFrame", - "is_group": true, - "children": [{ - "type": "boolean", - "key": "viewer_lut_raw", - "label": "Viewer LUT raw" - }] - }, { - "type": "dict", - "collapsable": true, - "key": "NukeSubmitDeadline", - "label": "NukeSubmitDeadline", - "is_group": true, - "children": [{ - "type": "number", - "key": "deadline_priority", - "label": "deadline_priority" - }, { - "type": "text", - "key": "deadline_pool", - "label": "deadline_pool" - }, { - "type": "text", - "key": "deadline_pool_secondary", - "label": "deadline_pool_secondary" - }, { - "type": "number", - "key": "deadline_chunk_size", - "label": "deadline_chunk_size" - }] - }] - }, { - "type": "raw-json", - "key": "workfile_build", - "label": "Workfile Build logic", - "is_file": true - }] - }, { - "type": "dict", - "collapsable": true, - "key": "nukestudio", - "label": "NukeStudio", - "children": [{ - "type": "dict", - "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [{ - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "CollectInstanceVersion", - "label": "Collect Instance Version", - "is_group": true, - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }] - }, { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ExtractReviewCutUpVideo", - "label": "Extract Review Cut Up Video", - "is_group": true, - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "list", - "object_type": "text", - "key": "tags_addition", - "label": "Tags addition" - }] - }] - }] - }, { - "type": "dict", - "collapsable": true, - "key": "resolve", - "label": "DaVinci Resolve", - "children": [{ - "type": "dict", - "collapsable": true, - "key": "create", - "label": "Creator plugins", - "is_file": true, - "children": [{ - "type": "dict", - "collapsable": true, - "key": "CreateShotClip", - "label": "Create Shot Clip", - "is_group": true, - "children": [{ - "type": "text", - "key": "clipName", - "label": "Clip name template" - }, { - "type": "text", - "key": "folder", - "label": "Folder" - }, { - "type": "number", - "key": "steps", - "label": "Steps" - }] - } - - ] - }] - }, - { - "type": "dict", - "collapsable": true, - "key": "standalonepublisher", - "label": "Standalone Publisher", - "children": [{ - "type": "dict", - "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [{ - "type": "dict", - "collapsable": true, - "key": "ExtractThumbnailSP", - "label": "ExtractThumbnailSP", - "is_group": true, - "children": [{ - "type": "dict", - "collapsable": false, - "key": "ffmpeg_args", - "label": "ffmpeg_args", - "children": [{ - "type": "list", - "object_type": "text", - "key": "input", - "label": "input" - }, - { - "type": "list", - "object_type": "text", - "key": "output", - "label": "output" - } - ] - }] - }] - }] - } - ] -} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_plugins.json b/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_plugins.json deleted file mode 100644 index 7ba9608610..0000000000 --- a/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_plugins.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "type": "dict", - "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": true, - "key": "ValidateModelName", - "label": "Validate Model Name", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "label", - "label": "Path to material file defining list of material names to check. This is material name per line simple text file.
It will be checked against named group shader in your Validation regex.

For example:
^.*(?P=<shader>.+)_GEO

" - }, - { - "type": "path-widget", - "key": "material_file", - "label": "Material File", - "multiplatform": true, - "multipath": false - }, - { - "type": "text", - "key": "regex", - "label": "Validation regex" - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "ValidateAssemblyName", - "label": "Validate Assembly Name", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "ValidateShaderName", - "label": "ValidateShaderName", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "label", - "label": "Shader name regex can use named capture group asset to validate against current asset name.

Example:
^.*(?P=<asset>.+)_SHD

" - - }, { - "type": "text", - "key": "regex", - "label": "Validation regex" - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "ValidateMeshHasOverlappingUVs", - "label": "ValidateMeshHasOverlappingUVs", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - } - ] - } - ] - } diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_workfiles.json b/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_workfiles.json deleted file mode 100644 index bae4d32abd..0000000000 --- a/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_workfiles.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "type": "raw-json", - "key": "workfile_build", - "label": "Workfile Build logic", - "is_file": true -} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_main.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_main.json new file mode 100644 index 0000000000..5b3c399666 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_main.json @@ -0,0 +1,74 @@ +{ + "key": "project", + "type": "dict", + "children": [ + { + "type": "anatomy", + "key": "project_anatomy", + "children": [ + { + "type": "anatomy_roots", + "key": "roots", + "is_file": true + }, + { + "type": "schema", + "name": "schema_anatomy_templates" + }, + { + "type": "schema", + "name": "schema_anatomy_attributes" + }, + { + "type": "schema", + "name": "schema_anatomy_imageio" + } + ] + }, { + "type": "dict", + "key": "project_settings", + "children": [ + { + "type": "schema", + "name": "schema_project_global" + }, + { + "type": "schema", + "name": "schema_project_ftrack" + }, + { + "type": "schema", + "name": "schema_project_maya" + }, + { + "type": "schema", + "name": "schema_project_nuke" + }, + { + "type": "schema", + "name": "schema_project_hiero" + }, + { + "type": "schema", + "name": "schema_project_harmony" + }, + { + "type": "schema", + "name": "schema_project_celaction" + }, + { + "type": "schema", + "name": "schema_project_resolve" + }, + { + "type": "schema", + "name": "schema_project_standalonepublisher" + }, + { + "type": "schema", + "name": "schema_project_unreal" + } + ] + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_plugins.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_plugins.json new file mode 100644 index 0000000000..f04750ff83 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_plugins.json @@ -0,0 +1,47 @@ +{ + "type": "dict", + "collapsable": true, + "key": "plugins", + "label": "Plugins", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "standalonepublisher", + "label": "Standalone Publisher", + "children": [{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [{ + "type": "dict", + "collapsable": true, + "key": "ExtractThumbnailSP", + "label": "ExtractThumbnailSP", + "is_group": true, + "children": [{ + "type": "dict", + "collapsable": false, + "key": "ffmpeg_args", + "label": "ffmpeg_args", + "children": [{ + "type": "list", + "object_type": "text", + "key": "input", + "label": "input" + }, + { + "type": "list", + "object_type": "text", + "key": "output", + "label": "output" + } + ] + }] + }] + }] + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_celaction.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_celaction.json new file mode 100644 index 0000000000..a1c94b14ef --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_celaction.json @@ -0,0 +1,50 @@ +{ + "type": "dict", + "collapsable": true, + "key": "celaction", + "label": "CelAction", + "is_file": true, + "children": [{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "children": [{ + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractCelactionDeadline", + "label": "ExtractCelactionDeadline", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "text", + "key": "deadline_department", + "label": "Deadline apartment" + }, { + "type": "number", + "key": "deadline_priority", + "label": "Deadline priority" + }, { + "type": "text", + "key": "deadline_pool", + "label": "Deadline pool" + }, { + "type": "text", + "key": "deadline_pool_secondary", + "label": "Deadline pool (secondary)" + }, { + "type": "text", + "key": "deadline_group", + "label": "Deadline Group" + }, { + "type": "number", + "key": "deadline_chunk_size", + "label": "Deadline Chunk size" + }] + }] + }] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_ftrack.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_ftrack.json new file mode 100644 index 0000000000..f54c1232a6 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_ftrack.json @@ -0,0 +1,266 @@ +{ + "type": "dict", + "key": "ftrack", + "label": "Ftrack", + "collapsable": true, + "checkbox_key": "enabled", + "is_file": true, + "children": [ + { + "type": "splitter" + }, + { + "type": "label", + "label": "Additional Ftrack paths" + }, + { + "type": "list", + "key": "ftrack_actions_path", + "label": "Action paths", + "object_type": "text" + }, + { + "type": "list", + "key": "ftrack_events_path", + "label": "Event paths", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "type": "dict", + "key": "events", + "label": "Server Events", + "children": [ + { + "type": "dict", + "key": "sync_to_avalon", + "label": "Sync to avalon", + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "Allow name and hierarchy change only if following statuses are on all children tasks" + }, + { + "type": "list", + "key": "statuses_name_change", + "label": "Statuses", + "object_type": { + "type": "text", + "multiline": false + } + } + ] + }, + { + "type": "dict", + "key": "push_frame_values_to_task", + "label": "Sync Hierarchical and Entity Attributes", + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "list", + "key": "interest_entity_types", + "label": "Entity types of interest", + "object_type": { + "type": "text", + "multiline": false + } + }, { + "type": "list", + "key": "interest_attributess", + "label": "Attributes to sync", + "object_type": { + "type": "text", + "multiline": false + } + }] + }, + { + "type": "dict", + "key": "thumbnail_updates", + "label": "Update Hierarchy thumbnails", + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + },{ + "type": "label", + "label": "Push thumbnail from version, up through multiple hierarchy levels." + },{ + "type": "number", + "key": "levels", + "label": "Levels" + }] + }, + { + "type": "dict", + "key": "user_assignment", + "label": "Run script on user assignments", + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }] + }, + { + "type": "dict", + "key": "status_update", + "label": "Update status on task action", + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "mapping", + "type": "dict-modifiable", + "object_type": { + "type": "list", + "object_type": "text" + } + }] + }, + { + "type": "dict", + "key": "status_task_to_parent", + "label": "Sync status from Task to Parent", + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "parent_status_match_all_task_statuses", + "type": "dict-modifiable", + "label": "Change parent if all tasks match", + "object_type": { + "type": "list", + "object_type": "text" + } + }, + { + "key": "parent_status_by_task_status", + "type": "dict-modifiable", + "label": "Change parent status if a single task matches", + "object_type": { + "type": "list", + "object_type": "text" + } + }] + }, + { + "type": "dict", + "key": "status_task_to_version", + "label": "Sync status from Task to Version", + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "dict-modifiable", + "key": "mapping", + "object_type": { + "type": "list", + "object_type": "text" + } + }] + }, + { + "type": "dict", + "key": "status_version_to_task", + "label": "Sync status from Version to Task", + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "dict-modifiable", + "key": "mapping", + "object_type": { + "type": "list", + "object_type": "text" + } + }] + }, + { + "type": "dict", + "key": "first_version_status", + "label": "Set status on first created version", + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + },{ + "type": "text", + "key": "status", + "label": "Status" + } + ] + }, + { + "type": "dict", + "key": "next_task_update", + "label": "Update status on next task", + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + },{ + "type": "dict-modifiable", + "key": "mapping", + "object_type": { + "type": "text" + } + }] + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [{ + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "IntegrateFtrackNote", + "label": "IntegrateFtrackNote", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "text", + "key": "note_with_intent_template", + "label": "Note with intent template" + }, { + "type": "list", + "object_type": "text", + "key": "note_labels", + "label": "Note labels" + }] + }] + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_global.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_global.json new file mode 100644 index 0000000000..75731fe207 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_global.json @@ -0,0 +1,28 @@ +{ + "type": "dict", + "collapsable": true, + "key": "global", + "label": "Global", + "is_file": true, + "children": [ + { + "type": "schema", + "name": "schema_global_publish" + }, + { + "type": "schema", + "name": "schema_global_tools" + }, + + { + "type": "collapsible-wrap", + "label": "Project Folder Structure", + "children": [ + { + "type": "raw-json", + "key": "project_folder_structure", + "label": "" + }] + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_harmony.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_harmony.json new file mode 100644 index 0000000000..282a4350b6 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_harmony.json @@ -0,0 +1,34 @@ +{ + "type": "dict", + "collapsable": true, + "key": "harmony", + "label": "Harmony", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "children": [] + }, + { + "type": "dict", + "collapsable": true, + "key": "general", + "label": "General", + "children": [ + + { + "type": "boolean", + "key": "skip_resolution_check", + "label": "Skip Resolution Check" + }, + { + "type": "boolean", + "key": "skip_timelines_check", + "label": "Skip Timeliene Check" + } + ] + }] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_hiero.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_hiero.json new file mode 100644 index 0000000000..834dc0bb3d --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_hiero.json @@ -0,0 +1,47 @@ +{ + "type": "dict", + "collapsable": true, + "key": "hiero", + "label": "Hiero", + "is_file": true, + "children": [{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "children": [{ + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "CollectInstanceVersion", + "label": "Collect Instance Version", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }] + }, { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractReviewCutUpVideo", + "label": "Extract Review Cut Up Video", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "list", + "object_type": "text", + "key": "tags_addition", + "label": "Tags addition" + }] + }] + }, + { + "type": "schema", + "name": "schema_publish_gui_filter" + }] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_maya.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_maya.json new file mode 100644 index 0000000000..a71cfb1e09 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_maya.json @@ -0,0 +1,27 @@ +{ + "type": "dict", + "collapsable": true, + "key": "maya", + "label": "Maya", + "is_file": true, + "children": [{ + "type": "schema", + "name": "schema_maya_capture" + }, + { + "type": "schema", + "name": "schema_maya_publish" + }, + { + "type": "schema", + "name": "schema_maya_load" + }, + { + "type": "schema", + "name": "schema_workfile_build" + }, + { + "type": "schema", + "name": "schema_publish_gui_filter" + }] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_nuke.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_nuke.json new file mode 100644 index 0000000000..3870fbe8bd --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_nuke.json @@ -0,0 +1,144 @@ +{ + "type": "dict", + "collapsable": true, + "key": "nuke", + "label": "Nuke", + "is_file": true, + "children": [{ + "type": "dict", + "collapsable": true, + "key": "create", + "label": "Create plugins", + "children": [{ + "type": "dict", + "collapsable": false, + "key": "CreateWriteRender", + "label": "CreateWriteRender", + "is_group": true, + "children": [{ + "type": "text", + "key": "fpath_template", + "label": "Path template" + }] + }, { + "type": "dict", + "collapsable": false, + "key": "CreateWritePrerender", + "label": "CreateWritePrerender", + "is_group": true, + "children": [{ + "type": "text", + "key": "fpath_template", + "label": "Path template" + }] + }] + }, { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "children": [{ + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractThumbnail", + "label": "ExtractThumbnail", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "raw-json", + "key": "nodes", + "label": "Nodes" + }] + }, { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ValidateNukeWriteKnobs", + "label": "ValidateNukeWriteKnobs", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "raw-json", + "key": "knobs", + "label": "Knobs" + }] + }, { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractReviewDataLut", + "label": "ExtractReviewDataLut", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }] + }, { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractReviewDataMov", + "label": "ExtractReviewDataMov", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "boolean", + "key": "viewer_lut_raw", + "label": "Viewer LUT raw" + }] + }, { + "type": "dict", + "collapsable": true, + "key": "ExtractSlateFrame", + "label": "ExtractSlateFrame", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "viewer_lut_raw", + "label": "Viewer LUT raw" + }] + }, { + "type": "dict", + "collapsable": true, + "key": "NukeSubmitDeadline", + "label": "NukeSubmitDeadline", + "is_group": true, + "children": [{ + "type": "number", + "key": "deadline_priority", + "label": "deadline_priority" + }, { + "type": "text", + "key": "deadline_pool", + "label": "deadline_pool" + }, { + "type": "text", + "key": "deadline_pool_secondary", + "label": "deadline_pool_secondary" + }, { + "type": "number", + "key": "deadline_chunk_size", + "label": "deadline_chunk_size" + }] + }] + }, + { + "type": "schema", + "name": "schema_workfile_build" + }, + { + "type": "schema", + "name": "schema_publish_gui_filter" + }] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_resolve.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_resolve.json new file mode 100644 index 0000000000..9452911354 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_resolve.json @@ -0,0 +1,35 @@ +{ + "type": "dict", + "collapsable": true, + "key": "resolve", + "label": "DaVinci Resolve", + "is_file": true, + "children": [{ + "type": "dict", + "collapsable": true, + "key": "create", + "label": "Creator plugins", + "children": [{ + "type": "dict", + "collapsable": true, + "key": "CreateShotClip", + "label": "Create Shot Clip", + "is_group": true, + "children": [{ + "type": "text", + "key": "clipName", + "label": "Clip name template" + }, { + "type": "text", + "key": "folder", + "label": "Folder" + }, { + "type": "number", + "key": "steps", + "label": "Steps" + }] + } + + ] + }] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_standalonepublisher.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_standalonepublisher.json new file mode 100644 index 0000000000..1241620dea --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_standalonepublisher.json @@ -0,0 +1,90 @@ +{ + "type": "dict", + "collapsable": true, + "key": "standalonepublisher", + "label": "Standalone Publisher", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "ExtractThumbnailSP", + "label": "ExtractThumbnailSP", + "is_group": true, + "children": [ + { + "type": "dict", + "collapsable": false, + "key": "ffmpeg_args", + "label": "ffmpeg_args", + "children": [ + { + "type": "list", + "object_type": "text", + "key": "input", + "label": "input" + }, + { + "type": "list", + "object_type": "text", + "key": "output", + "label": "output" + }] + }] + }] + }, + { + "type": "dict-modifiable", + "collapsable": true, + "key": "create", + "label": "Creator plugins", + "collapsable_key": true, + "is_file": true, + "object_type": + { + "type": "dict", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "text", + "key": "label", + "label": "Label" + }, + { + "type": "text", + "key": "family", + "label": "Family" + }, + { + "type": "text", + "key": "icon", + "label": "Icon" + }, + { + "type": "list", + "key": "defaults", + "label": "Defaults", + "object_type": + { + "type": "text" + } + }, + { + "type": "text", + "key": "help", + "label": "Help" + }] + } + }] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_unreal.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_unreal.json new file mode 100644 index 0000000000..63cfdec639 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_unreal.json @@ -0,0 +1,25 @@ +{ + "type": "dict", + "collapsable": true, + "key": "unreal", + "label": "Unreal Engine", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "project_setup", + "label": "Project Setup", + "children": [ + { + "type": "boolean", + "key": "dev_mode", + "label": "Dev mode" + }, + { + "type": "boolean", + "key": "install_unreal_python_engine", + "label": "Install unreal python engine" + }] + }] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_anatomy_attributes.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_anatomy_attributes.json new file mode 100644 index 0000000000..a64b99ce9d --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_anatomy_attributes.json @@ -0,0 +1,79 @@ +{ + "type": "dict", + "collapsable": true, + "key": "attributes", + "label": "Attributes", + "is_file": true, + "children": [ + { + "type": "number", + "key": "fps", + "label": "Frame Rate" + }, + { + "type": "number", + "key": "frameStart", + "label": "Frame Start" + }, + { + "type": "number", + "key": "frameEnd", + "label": "Frame End" + }, + { + "type": "number", + "key": "clipIn", + "label": "Clip In" + }, + { + "type": "number", + "key": "clipOut", + "label": "Clip Out" + }, + { + "type": "number", + "key": "handleStart", + "label": "Handle Start" + }, + { + "type": "number", + "key": "handleEnd", + "label": "Handle End" + }, + { + "type": "number", + "key": "resolutionWidth", + "label": "Resolution Width" + }, + { + "type": "number", + "key": "resolutionHeight", + "label": "Resolution Height" + }, + { + "type": "number", + "key": "pixelAspect", + "label": "Pixel Aspect Ratio" + }, + { + "type": "enum", + "key": "applications", + "label": "Applications", + "multiselection": true, + "enum_items": [ + {"maya_2020" : "Maya 2020"}, + {"nuke_12.2": "Nuke 12.2"}, + {"hiero_12.2": "Hiero 12.2"}, + {"houdini_18": "Houdini 18"}, + {"blender_2.91": "Blender 2.91"}, + {"aftereffects_2021": "After Effects 2021"} + ] + }, + { + "type": "dict-modifiable", + "key": "task_short_names", + "label": "Task short names (by Task type)", + "object_type": "text" + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_anatomy_imageio.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_anatomy_imageio.json new file mode 100644 index 0000000000..f3de0dcb44 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_anatomy_imageio.json @@ -0,0 +1,289 @@ +{ + "type": "dict", + "key": "imageio", + "label": "Color Management and Output Formats", + "is_file": true, + "children": [{ + "key": "hiero", + "type": "dict", + "label": "Hiero", + "children": [{ + "key": "workfile", + "type": "dict", + "label": "Workfile", + "collapsable": false, + "children": [{ + "type": "form", + "children": [{ + "type": "enum", + "key": "ocioConfigName", + "label": "OpenColorIO Config", + "enum_items": [{ + "nuke-default": "nuke-default" + }, + { + "aces_1.0.3": "aces_1.0.3" + }, + { + "aces_1.1": "aces_1.1" + }, + { + "custom": "custom" + } + ] + }, { + "type": "path-widget", + "key": "ocioconfigpath", + "label": "Custom OCIO path", + "multiplatform": true, + "multipath": true + }, { + "type": "text", + "key": "workingSpace", + "label": "Working Space" + }, { + "type": "text", + "key": "sixteenBitLut", + "label": "16 Bit Files" + }, { + "type": "text", + "key": "eightBitLut", + "label": "8 Bit Files" + }, { + "type": "text", + "key": "floatLut", + "label": "Floating Point Files" + }, { + "type": "text", + "key": "logLut", + "label": "Log Files" + }, { + "type": "text", + "key": "viewerLut", + "label": "Viewer" + }, { + "type": "text", + "key": "thumbnailLut", + "label": "Thumbnails" + }] + }] + }, { + "key": "regexInputs", + "type": "dict", + "label": "Colorspace on Inputs by regex detection", + "collapsable": true, + "children": [{ + "type": "list", + "key": "inputs", + "label": "", + "object_type": { + "type": "dict", + "children": [{ + "type": "text", + "key": "regex", + "label": "Regex" + }, { + "type": "text", + "key": "colorspace", + "label": "Colorspace" + }] + } + }] + }] + }, { + "key": "nuke", + "type": "dict", + "label": "Nuke", + "children": [{ + "key": "workfile", + "type": "dict", + "label": "Workfile", + "collapsable": false, + "is_group": true, + "children": [{ + "type": "form", + "children": [{ + "type": "enum", + "key": "colorManagement", + "label": "color management", + "enum_items": [{ + "Nuke": "Nuke" + }, + { + "OCIO": "OCIO" + } + ] + }, { + "type": "enum", + "key": "OCIO_config", + "label": "OpenColorIO Config", + "enum_items": [{ + "nuke-default": "nuke-default" + }, + { + "spi-vfx": "spi-vfx" + }, + { + "spi-anim": "spi-anim" + }, + { + "aces_1.0.3": "aces_0.1.1" + }, + { + "aces_1.0.3": "aces_0.7.1" + }, + { + "aces_1.0.3": "aces_1.0.1" + }, + { + "aces_1.0.3": "aces_1.0.3" + }, + { + "aces_1.1": "aces_1.1" + }, + { + "custom": "custom" + } + ] + }, { + "type": "path-widget", + "key": "customOCIOConfigPath", + "label": "Custom OCIO config path", + "multiplatform": true, + "multipath": true + }, { + "type": "text", + "key": "workingSpaceLUT", + "label": "Working Space" + }, { + "type": "text", + "key": "monitorLut", + "label": "monitor" + }, { + "type": "text", + "key": "int8Lut", + "label": "8-bit files" + }, { + "type": "text", + "key": "int16Lut", + "label": "16-bit files" + }, { + "type": "text", + "key": "logLut", + "label": "log files" + }, { + "type": "text", + "key": "floatLut", + "label": "float files" + }] + }] + }, { + "key": "nodes", + "type": "dict", + "label": "Nodes", + "collapsable": true, + "is_group": true, + "children": [{ + "key": "requiredNodes", + "type": "list", + "label": "Required Nodes", + "object_type": { + "type": "dict", + "children": [{ + "type": "list", + "key": "plugins", + "label": "Used in plugins", + "object_type": { + "type": "text", + "key": "pluginClass", + "label": "Plugin Class" + } + }, { + "type": "text", + "key": "nukeNodeClass", + "label": "Nuke Node Class" + }, { + "type": "splitter" + }, { + "key": "knobs", + "label": "Knobs", + "type": "list", + "object_type": { + "type": "dict", + "children": [{ + "type": "text", + "key": "name", + "label": "Name" + }, { + "type": "text", + "key": "value", + "label": "Value" + }] + } + }] + } + }, { + "type": "list", + "key": "customNodes", + "label": "Custom Nodes", + "object_type": { + "type": "dict", + "children": [{ + "type": "list", + "key": "plugins", + "label": "Used in plugins", + "object_type": { + "type": "text", + "key": "pluginClass", + "label": "Plugin Class" + } + }, { + "type": "text", + "key": "nukeNodeClass", + "label": "Nuke Node Class" + }, { + "type": "splitter" + }, { + "key": "knobs", + "label": "Knobs", + "type": "list", + "object_type": { + "type": "dict", + "children": [{ + "type": "text", + "key": "name", + "label": "Name" + }, { + "type": "text", + "key": "value", + "label": "Value" + }] + } + }] + } + }] + }, { + "key": "regexInputs", + "type": "dict", + "label": "Colorspace on Inputs by regex detection", + "collapsable": true, + "children": [{ + "type": "list", + "key": "inputs", + "label": "", + "object_type": { + "type": "dict", + "children": [{ + "type": "text", + "key": "regex", + "label": "Regex" + }, { + "type": "text", + "key": "colorspace", + "label": "Colorspace" + }] + } + }] + }] + }] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_anatomy_templates.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_anatomy_templates.json new file mode 100644 index 0000000000..2649178bff --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_anatomy_templates.json @@ -0,0 +1,136 @@ +{ + "type": "dict", + "collapsable": true, + "key": "templates", + "label": "Templates", + "collapsable_key": true, + "is_file": true, + "children": [ + { + "type": "number", + "key": "version_padding", + "label": "Version Padding" + }, + { + "type": "text", + "key": "version", + "label": "Version" + }, + { + "type": "number", + "key": "frame_padding", + "label": "Frame Padding" + }, + { + "type": "text", + "key": "frame", + "label": "Frame" + }, + { + "type": "dict", + "key": "work", + "label": "Work", + "children": [ + { + "type": "text", + "key": "folder", + "label": "Folder" + }, + { + "type": "text", + "key": "file", + "label": "File" + }, + { + "type": "text", + "key": "path", + "label": "Path" + }] + }, + { + "type": "dict", + "key": "render", + "label": "Render", + "children": [ + { + "type": "text", + "key": "folder", + "label": "Folder" + }, + { + "type": "text", + "key": "file", + "label": "File" + }, + { + "type": "text", + "key": "path", + "label": "Path" + } + + ] + }, + { + "type": "dict", + "key": "publish", + "label": "Publish", + "children": [ + { + "type": "text", + "key": "folder", + "label": "Folder" + }, + { + "type": "text", + "key": "file", + "label": "File" + }, + { + "type": "text", + "key": "path", + "label": "Path" + }, + { + "type": "text", + "key": "thumbnail", + "label": "Thumbnail" + }] + }, + { + "type": "dict", + "key": "master", + "label": "Master", + "children": [ + { + "type": "text", + "key": "folder", + "label": "Folder" + }, + { + "type": "text", + "key": "file", + "label": "File" + }, + { + "type": "text", + "key": "path", + "label": "Path" + } + + ] + }, + { + "type": "dict-modifiable", + "key": "delivery", + "label": "Delivery", + "object_type": "text" + }, + { + "type": "dict-modifiable", + "key": "other", + "label": "Other", + "object_type": "text" + } + + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_global_publish.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_global_publish.json new file mode 100644 index 0000000000..86c6f2963e --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_global_publish.json @@ -0,0 +1,399 @@ +{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "children": [ + { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "IntegrateMasterVersion", + "label": "IntegrateMasterVersion", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }] + }, + { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractJpegEXR", + "label": "ExtractJpegEXR", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "dict", + "key": "ffmpeg_args", + "children": [ + { + "type": "list", + "object_type": "text", + "key": "input", + "label": "FFmpeg input arguments" + }, + { + "type": "list", + "object_type": "text", + "key": "output", + "label": "FFmpeg output arguments" + }] + }] + }, + { + "type": "dict", + "collapsable": true, + "key": "ExtractReview", + "label": "ExtractReview", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "profiles", + "label": "Profiles", + "object_type": + { + "type": "dict", + "children": [ + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "hosts", + "label": "Hosts", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "key": "outputs", + "label": "Output Definitions", + "type": "dict-modifiable", + "highlight_content": true, + "object_type": + { + "type": "dict", + "children": [ + { + "key": "ext", + "label": "Output extension", + "type": "text" + }, + { + "key": "tags", + "label": "Tags", + "type": "enum", + "multiselection": true, + "enum_items": [ + { + "burnin": "Add burnins" + }, + { + "ftrackreview": "Add to Ftrack" + }, + { + "delete": "Delete output" + }, + { + "slate-frame": "Add slate frame" + }, + { + "no-hnadles": "Skip handle frames" + }] + }, + { + "key": "ffmpeg_args", + "label": "FFmpeg arguments", + "type": "dict", + "highlight_content": true, + "children": [ + { + "key": "video_filters", + "label": "Video filters", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "key": "audio_filters", + "label": "Audio filters", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "key": "input", + "label": "Input arguments", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "key": "output", + "label": "Output arguments", + "type": "list", + "object_type": "text" + }] + }, + { + "key": "filter", + "label": "Additional output filtering", + "type": "dict", + "highlight_content": true, + "children": [ + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }] + }] + } + }] + } + }] + }, + { + "type": "dict", + "collapsable": true, + "key": "ExtractBurnin", + "label": "ExtractBurnin", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "dict", + "collapsable": true, + "key": "options", + "label": "Burnin formating options", + "children": [ + { + "type": "number", + "key": "font_size", + "label": "Font size" + }, + { + "type": "number", + "key": "opacity", + "label": "Font opacity" + }, + { + "type": "number", + "key": "bg_opacity", + "label": "Background opacity" + }, + { + "type": "number", + "key": "x_offset", + "label": "X Offset" + }, + { + "type": "number", + "key": "y_offset", + "label": "Y Offset" + }, + { + "type": "number", + "key": "bg_padding", + "label": "Padding aroung text" + }, + { + "type": "splitter" + }] + }, + + { + "type": "list", + "key": "profiles", + "label": "Profiles", + "object_type": + { + "type": "dict", + "children": [ + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "hosts", + "label": "Hosts", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "key": "burnins", + "label": "Burnins", + "type": "dict-modifiable", + "highlight_content": true, + "collapsable": false, + "object_type": + { + "type": "dict", + "children": [ + { + "key": "TOP_LEFT", + "label": "Top Left", + "type": "text" + }, + { + "key": "TOP_CENTERED", + "label": "Top Centered", + "type": "text" + }, + { + "key": "TOP_RIGHT", + "label": "top Right", + "type": "text" + }, + { + "key": "BOTTOM_LEFT", + "label": "Bottom Left", + "type": "text" + }, + { + "key": "BOTTOM_CENTERED", + "label": "Bottom Centered", + "type": "text" + }, + { + "key": "BOTTOM_RIGHT", + "label": "BottomRight", + "type": "text" + }] + } + }] + } + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "IntegrateAssetNew", + "label": "IntegrateAssetNew", + "is_group": true, + "children": [ + { + "type": "raw-json", + "key": "template_name_profiles", + "label": "template_name_profiles" + }] + }, + { + "type": "dict", + "collapsable": true, + "key": "ProcessSubmittedJobOnFarm", + "label": "ProcessSubmittedJobOnFarm", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "deadline_department", + "label": "Deadline department" + }, + { + "type": "text", + "key": "deadline_pool", + "label": "Deadline Pool" + }, + { + "type": "text", + "key": "deadline_group", + "label": "Deadline Group" + }, + { + "type": "text", + "key": "deadline_chunk_size", + "label": "Deadline Chunk Size" + }, + { + "type": "text", + "key": "deadline_priority", + "label": "Deadline Priotity" + }, + { + "type": "dict", + "key": "aov_filter", + "label": "Reviewable subsets filter", + "children": [ + { + "type": "list", + "key": "maya", + "label": "Maya", + "object_type": + { + "type": "text" + } + }, + { + "type": "list", + "key": "nuke", + "label": "Nuke", + "object_type": + { + "type": "text" + } + }, + { + "type": "list", + "key": "aftereffects", + "label": "After Effects", + "object_type": + { + "type": "text" + } + }, + { + "type": "list", + "key": "celaction", + "label": "Celaction", + "object_type": + { + "type": "text" + } + }] + }] + }] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_global_tools.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_global_tools.json new file mode 100644 index 0000000000..529794fd28 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_global_tools.json @@ -0,0 +1,80 @@ +{ + "type": "dict", + "collapsable": true, + "key": "tools", + "label": "Tools", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "Creator", + "label": "Creator", + "children": [ + { + "type": "dict-modifiable", + "collapsable": false, + "key": "families_smart_select", + "label": "Families smart select", + "object_type":{ + "type": "list", + "object_type": "text" + } + }] + }, + { + "type": "dict", + "collapsable": true, + "key": "Workfiles", + "label": "Workfiles", + "children": [ + { + "type": "collapsible-wrap", + "label": "Open last workfiles on launch", + "children": [ + { + "type": "list", + "key": "last_workfile_on_startup", + "label": "", + "is_group": true, + "object_type": + { + "type": "dict", + "children": [ + { + "key": "hosts", + "label": "Hosts", + "type": "list", + "object_type": "text" + }, + { + "key": "tasks", + "label": "Tasks", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + } + }] + }, + { + "type": "dict-modifiable", + "collapsable": true, + "key": "sw_folders", + "label": "Extra task folders", + "is_group": true, + "object_type":{ + "type": "list", + "object_type": "text" + } + } + ] + }] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_capture.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_maya_capture.json similarity index 94% rename from pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_capture.json rename to pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_maya_capture.json index 836ad90404..314fdc7514 100644 --- a/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_capture.json +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_maya_capture.json @@ -2,14 +2,12 @@ "type": "dict", "collapsable": true, "key": "maya_capture", - "label": "Maya Capture settings", + "label": "Maya Playblast settings", "is_file": true, "children": [ { - "type": "dict-invisible", + "type": "dict", "key": "Codec", - "label": "Codec", - "collapsable": false, "children": [ { "type": "label", @@ -37,10 +35,8 @@ } ] }, { - "type": "dict-invisible", + "type": "dict", "key": "Display Options", - "label": "Display Options", - "collapsable": false, "children": [ { "type": "label", @@ -132,10 +128,8 @@ "type": "splitter" } , { - "type": "dict-invisible", - "collapsable": true, + "type": "dict", "key": "Generic", - "label": "Generic", "children": [ { "type": "label", @@ -152,10 +146,8 @@ } ] },{ - "type": "dict-invisible", - "collapsable": true, + "type": "dict", "key": "IO", - "label": "IO", "children": [ { "type": "label", @@ -185,10 +177,8 @@ } ] },{ - "type": "dict-invisible", - "collapsable": true, + "type": "dict", "key": "PanZoom", - "label": "Pan Zoom", "children": [ { "type": "boolean", @@ -200,10 +190,8 @@ { "type": "splitter" },{ - "type": "dict-invisible", - "collapsable": true, + "type": "dict", "key": "Renderer", - "label": "Renderer", "children": [ { @@ -217,10 +205,8 @@ } ] },{ - "type": "dict-invisible", - "collapsable": true, + "type": "dict", "key": "Resolution", - "label": "Resolution", "children": [ { @@ -262,10 +248,8 @@ "type": "splitter" }, { - "type": "dict-invisible", - "collapsable": true, + "type": "dict", "key": "Time Range", - "label": "Time Range", "children": [ { "type": "label", diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_maya_load.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_maya_load.json new file mode 100644 index 0000000000..28750eb795 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_maya_load.json @@ -0,0 +1,141 @@ +{ + "type": "dict", + "collapsable": true, + "key": "load", + "label": "Loader plugins", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "colors", + "label": "Loaded Subsets Outliner Colors", + "children": [ + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Model", + "name": "model" + }] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Rig", + "name": "rig" + }] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Pointcache", + "name": "pointcache" + }] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Animation", + "name": "animation" + }] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Arnold Standin", + "name": "ass" + }] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Camera", + "name": "camera" + }] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "FBX", + "name": "fbx" + }] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Maya Scene", + "name": "mayaAscii" + }] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Set Dress", + "name": "setdress" + }] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Layout", + "name": "layout" + }] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "VDB Cache", + "name": "vdbcache" + }] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Vray Proxy", + "name": "vrayproxy" + }] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Yeti Cache", + "name": "yeticache" + }] + }, + { + "type": "schema_template", + "name": "template_color", + "template_data": [ + { + "label": "Yeti Rig", + "name": "yetiRig" + }] + } + ] + }] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_maya_publish.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_maya_publish.json new file mode 100644 index 0000000000..33d0a06d2c --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_maya_publish.json @@ -0,0 +1,187 @@ +{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "children": [ + { + "type": "label", + "label": "Collectors" + }, + { + "type": "dict", + "collapsable": true, + "key": "CollectMayaRender", + "label": "Collect Render Layers", + "children": [ + { + "type": "boolean", + "key": "sync_workfile_version", + "label": "Sync render version with workfile" + }] + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "Collectors" + }, + { + "type": "dict", + "collapsable": true, + "key": "ValidateCameraAttributes", + "label": "Validate Camera Attributes", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }] + }, + { + "type": "dict", + "collapsable": true, + "key": "ValidateModelName", + "label": "Validate Model Name", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "Path to material file defining list of material names to check. This is material name per line simple text file.
It will be checked against named group shader in your Validation regex.

For example:
^.*(?P=<shader>.+)_GEO

" + }, + { + "type": "path-widget", + "key": "material_file", + "label": "Material File", + "multiplatform": true, + "multipath": false + }, + { + "type": "text", + "key": "regex", + "label": "Validation regex" + }] + }, + { + "type": "dict", + "collapsable": true, + "key": "ValidateAssemblyName", + "label": "Validate Assembly Name", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }] + }, + { + "type": "dict", + "collapsable": true, + "key": "ValidateShaderName", + "label": "ValidateShaderName", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "Shader name regex can use named capture group asset to validate against current asset name.

Example:
^.*(?P=<asset>.+)_SHD

" + + }, + { + "type": "text", + "key": "regex", + "label": "Validation regex" + }] + }, + { + "type": "dict", + "collapsable": true, + "key": "ValidateMeshHasOverlappingUVs", + "label": "ValidateMeshHasOverlappingUVs", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }] + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "Extractors" + }, + { + "type": "dict", + "collapsable": true, + "key": "ExtractCameraAlembic", + "label": "Extract camera to Alembic", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "List of attributes that will be added to the baked alembic camera. Needs to be written in python list syntax.

For example:
[\"attributeName\", \"anotherAttribute\"]

" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "raw-json", + "key": "bake_attributes", + "label": "Bake Attributes" + + }] + }, + { + "type": "dict", + "collapsable": true, + "key": "MayaSubmitDeadline", + "label": "Submit maya job to deadline", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "enum", + "key": "tile_assembler_plugin", + "label": "Tile Assembler Plugin", + "multiselection": false, + "enum_items": [ + { + "DraftTileAssembler": "Draft Tile Assembler" + }, + { + "oiio": "Open Image IO" + }] + }] + }] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_publish_gui_filter.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_publish_gui_filter.json new file mode 100644 index 0000000000..2acb5730a3 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_publish_gui_filter.json @@ -0,0 +1,11 @@ +{ + "type": "dict-modifiable", + "collapsable": true, + "key": "filters", + "label": "Publish GUI Filters", + "object_type": + { + "type": "raw-json", + "label": "Plugins" + } +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_workfile_build.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_workfile_build.json new file mode 100644 index 0000000000..054f009ad4 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/schema_workfile_build.json @@ -0,0 +1,81 @@ +{ + "type": "dict", + "collapsable": true, + "key": "workfile_build", + "label": "Workfile Build Settings", + "children": [{ + "type": "list", + "key": "profiles", + "label": "Profiles", + "object_type": { + "type": "dict", + "children": [{ + "key": "tasks", + "label": "Tasks", + "type": "list", + "object_type": "text" + }, { + "type": "splitter" + }, { + "key": "current_context", + "label": "Current Context", + "type": "list", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [{ + "key": "subset_name_filters", + "label": "Subset name Filters", + "type": "list", + "object_type": "text" + }, { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + },{ + "key": "repre_names", + "label": "Repre Names", + "type": "list", + "object_type": "text" + },{ + "key": "loaders", + "label": "Loaders", + "type": "list", + "object_type": "text" + }] + } + }, + { + "key": "linked_assets", + "label": "Linked Assets", + "type": "list", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [{ + "key": "subset_name_filters", + "label": "Subset name Filters", + "type": "list", + "object_type": "text" + }, { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + },{ + "key": "repre_names", + "label": "Repre Names", + "type": "list", + "object_type": "text" + },{ + "key": "loaders", + "label": "Loaders", + "type": "list", + "object_type": "text" + }] + } + }] + } + }] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/template_color.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/template_color.json new file mode 100644 index 0000000000..ac4313490b --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schemas/template_color.json @@ -0,0 +1,28 @@ +[ + { + "type": "list-strict", + "key": "{name}", + "label": "{label}:", + "object_types": [ + { + "label": "Red", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, { + "label": "Green", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, { + "label": "Blue", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + } + ] + } +] diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/example_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/example_schema.json index 7612e54116..9cbb214d86 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/example_schema.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/example_schema.json @@ -13,16 +13,16 @@ "type": "schema_template", "name": "example_template", "template_data": { - "host_label": "Maya 2019", - "host_name": "maya_2019", + "host_label": "Application 1", + "host_name": "app_1", "multipath_executables": false } }, { "type": "schema_template", "name": "example_template", "template_data": { - "host_label": "Maya 2020", - "host_name": "maya_2020" + "host_label": "Application 2", + "host_name": "app_2" } } ] @@ -40,7 +40,7 @@ ] }, { "key": "dict_wrapper", - "type": "dict-invisible", + "type": "dict", "children": [ { "type": "enum", @@ -248,7 +248,7 @@ "key": "dict_item", "label": "DictItem in List", "object_type": { - "type": "dict-item", + "type": "dict", "children": [ { "key": "families", @@ -418,6 +418,16 @@ ] } ] + }, { + "type": "collapsible-wrap", + "label": "Collapsible Wrapper without key", + "children": [ + { + "type": "text", + "key": "_example_input_collapsible", + "label": "Example input in collapsible wrapper" + } + ] } ] } diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_aftereffects.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_aftereffects.json new file mode 100644 index 0000000000..073d57b870 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_aftereffects.json @@ -0,0 +1,41 @@ +{ + "type": "dict", + "key": "aftereffects", + "label": "Adobe AfterEffects", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "schema_template", + "name": "template_host_unchangables" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "aftereffects" + }, + { + "type": "dict-invisible", + "key": "variants", + "children": [{ + "type": "schema_template", + "name": "template_host_variant", + "template_data": [ + { + "host_version": "2020", + "host_name": "aftereffects" + }, + { + "host_version": "2021", + "host_name": "aftereffects" + } + ] + }] + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_blender.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_blender.json index 28adf347b1..1cdd6825bc 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_blender.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_blender.json @@ -20,7 +20,7 @@ "env_group_key": "blender" }, { - "type": "dict-invisible", + "type": "dict", "key": "variants", "children": [{ "type": "schema_template", diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_celaction.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_celaction.json index ac872267d8..8a78aaf187 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_celaction.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_celaction.json @@ -20,7 +20,7 @@ "env_group_key": "celaction" }, { - "type": "dict-invisible", + "type": "dict", "key": "variants", "children": [{ "type": "schema_template", diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_djv.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_djv.json index 987bb382db..4ebb97d5bd 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_djv.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_djv.json @@ -20,7 +20,7 @@ "env_group_key": "djvview" }, { - "type": "dict-invisible", + "type": "dict", "key": "variants", "children": [{ "type": "schema_template", diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_fusion.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_fusion.json index b2ce01b7db..38fdc2d067 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_fusion.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_fusion.json @@ -20,7 +20,7 @@ "env_group_key": "fusion" }, { - "type": "dict-invisible", + "type": "dict", "key": "variants", "children": [{ "type": "schema_template", diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_harmony.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_harmony.json index 80bc9864f0..8ca793f90b 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_harmony.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_harmony.json @@ -20,7 +20,7 @@ "env_group_key": "harmony" }, { - "type": "dict-invisible", + "type": "dict", "key": "variants", "children": [{ "type": "schema_template", @@ -30,14 +30,6 @@ "host_version": "20", "host_name": "harmony" }, - { - "host_version": "19", - "host_name": "harmony" - }, - { - "host_version": "18", - "host_name": "harmony" - }, { "host_version": "17", "host_name": "harmony" diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_houdini.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_houdini.json index ace2a3106c..be319198ba 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_houdini.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_houdini.json @@ -20,7 +20,7 @@ "env_group_key": "houdini" }, { - "type": "dict-invisible", + "type": "dict", "key": "variants", "children": [{ "type": "schema_template", diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_maya.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_maya.json index eb055863b8..4ecf2362fa 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_maya.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_maya.json @@ -21,7 +21,7 @@ "env_group_key": "maya" }, { - "type": "dict-invisible", + "type": "dict", "key": "variants", "children": [{ "type": "schema_template", diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_mayabatch.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_mayabatch.json index 27d2ca68cd..43d281991a 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_mayabatch.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_mayabatch.json @@ -21,7 +21,7 @@ "env_group_key": "mayabatch" }, { - "type": "dict-invisible", + "type": "dict", "key": "variants", "children": [{ "type": "schema_template", diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_photoshop.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_photoshop.json index f86f6ff055..dd8e4008be 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_photoshop.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_photoshop.json @@ -20,7 +20,7 @@ "env_group_key": "photoshop" }, { - "type": "dict-invisible", + "type": "dict", "key": "variants", "children": [{ "type": "schema_template", @@ -29,6 +29,10 @@ { "host_version": "2020", "host_name": "photoshop" + }, + { + "host_version": "2021", + "host_name": "photoshop" } ] }] diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_resolve.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_resolve.json index e6d0a6a84d..4871d0ebb8 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_resolve.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_resolve.json @@ -20,7 +20,7 @@ "env_group_key": "resolve" }, { - "type": "dict-invisible", + "type": "dict", "key": "variants", "children": [{ "type": "schema_template", diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_shell.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_shell.json index bbc86c53ee..389d480a45 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_shell.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_shell.json @@ -16,7 +16,7 @@ "env_group_key": "shell" }, { - "type": "dict-invisible", + "type": "dict", "key": "variants", "children": [{ "type": "schema_template", diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_tvpaint.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_tvpaint.json new file mode 100644 index 0000000000..09e5b1d907 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_tvpaint.json @@ -0,0 +1,41 @@ +{ + "type": "dict", + "key": "tvpaint", + "label": "TVPaint", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "schema_template", + "name": "template_host_unchangables" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "tvpaint" + }, + { + "type": "dict-invisible", + "key": "variants", + "children": [{ + "type": "schema_template", + "name": "template_host_variant", + "template_data": [ + { + "host_version": "Animation 11 (64bits)", + "host_name": "tvpaint" + }, + { + "host_version": "Animation 11 (32bits)", + "host_name": "tvpaint" + } + ] + }] + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_unreal.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_unreal.json index 6c2778f470..859d73614b 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_unreal.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/schema_unreal.json @@ -20,7 +20,7 @@ "env_group_key": "unreal" }, { - "type": "dict-invisible", + "type": "dict", "key": "variants", "children": [{ "type": "schema_template", diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/template_host_unchangables.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/template_host_unchangables.json index 732fd06c30..5fde8e9c1e 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/template_host_unchangables.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/template_host_unchangables.json @@ -13,8 +13,24 @@ "roles": ["developer"] }, { - "type": "boolean", - "key": "is_host", - "label": "Has host implementation", + "type": "enum", + "key": "host_name", + "label": "Host implementation", + "enum_items": [ + {"": "< without host >"}, + {"aftereffects": "aftereffects"}, + {"blender": "blender"}, + {"celaction": "celaction"}, + {"fusion": "fusion"}, + {"harmony": "harmony"}, + {"hiero": "hiero"}, + {"houdini": "houdini"}, + {"maya": "maya"}, + {"nuke": "nuke"}, + {"photoshop": "photoshop"}, + {"resolve": "resolve"}, + {"tvpaint": "tvpaint"}, + {"unreal": "unreal"} + ], "roles": ["developer"] }] diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/template_host_variant.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/template_host_variant.json index ce3a75e871..cea7da3a81 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/template_host_variant.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/template_host_variant.json @@ -43,7 +43,8 @@ "key": "executables", "label": "Executables", "multiplatform": "{multiplatform}", - "multipath": "{multipath_executables}" + "multipath": "{multipath_executables}", + "with_arguments": true }, { "key": "environment", diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/template_nuke.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/template_nuke.json index e8a8fc3799..c00f8ae266 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/template_nuke.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/template_nuke.json @@ -20,12 +20,17 @@ "env_group_key": "{nuke_type}" }, { - "type": "dict-invisible", + "type": "dict", "key": "variants", "children": [{ "type": "schema_template", "name": "template_host_variant", "template_data": [ + { + "host_version": "12.2", + "host_name": "{nuke_type}", + "multipath_executables": true + }, { "host_version": "12.0", "host_name": "{nuke_type}", diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/module_settings/schema_ftrack.json b/pype/tools/settings/settings/gui_schemas/system_schema/module_settings/schema_ftrack.json new file mode 100644 index 0000000000..58cd81f544 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/module_settings/schema_ftrack.json @@ -0,0 +1,164 @@ +{ + "type": "dict", + "key": "Ftrack", + "label": "Ftrack", + "collapsable": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "ftrack_server", + "label": "Server" + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "Additional Ftrack paths" + }, + { + "type": "list", + "key": "ftrack_actions_path", + "label": "Action paths", + "object_type": "text" + }, + { + "type": "list", + "key": "ftrack_events_path", + "label": "Event paths", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "Ftrack event server advanced settings" + }, + { + "type": "text", + "key": "FTRACK_EVENTS_MONGO_DB", + "label": "Event Mongo DB" + }, + { + "type": "text", + "key": "FTRACK_EVENTS_MONGO_COL", + "label": "Events Mongo Collection" + }, + { + "type": "splitter" + }, + { + "key": "intent", + "type": "dict", + "label": "Intent", + "collapsable_key": true, + "is_group": true, + "children": [ + { + "type": "label", + "label": "Intent" + }, + { + "type": "dict-modifiable", + "object_type": "text", + "key": "items" + }, + { + "type": "label", + "label": " " + }, + { + "key": "default", + "type": "text", + "label": "Default Intent" + }] + }, + { + "key": "custom_attributes", + "label": "Custom Attributes", + "type": "dict", + "children": [ + { + "type": "dict-modifiable", + "label": "Show Attributes", + "key": "show", + "object_type": + { + "type": "dict", + "children": [ + { + "key": "default", + "label": "default", + "type": "text" + }, + { + "key": "write_security_role", + "label": "write", + "type": "list", + "object_type": + { + "type": "text" + } + }, + { + "key": "read_security_role", + "label": "Read", + "type": "list", + "object_type": + { + "type": "text" + } + }] + } + }, + { + "type": "dict-modifiable", + "label": "Hierarchical Attributes", + "key": "is_hierarchical", + "object_type": + { + "type": "dict", + "children": [ + { + "key": "default", + "label": "default", + "type": "text" + }, + { + "key": "write_security_role", + "label": "write", + "type": "list", + "object_type": + { + "type": "text" + } + }, + { + "key": "read_security_role", + "label": "Read", + "type": "list", + "object_type": + { + "type": "text" + } + }] + } + }] + }, + { + "type": "splitter" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "ftrack" + }] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/schema_applications.json b/pype/tools/settings/settings/gui_schemas/system_schema/schema_applications.json index ebfa4482bb..1c983bcff2 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/schema_applications.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/schema_applications.json @@ -65,10 +65,18 @@ "type": "schema", "name": "schema_harmony" }, + { + "type": "schema", + "name": "schema_tvpaint" + }, { "type": "schema", "name": "schema_photoshop" }, + { + "type": "schema", + "name": "schema_aftereffects" + }, { "type": "schema", "name": "schema_celaction" diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/schema_main.json b/pype/tools/settings/settings/gui_schemas/system_schema/schema_main.json index 8e8798149c..ae6622098e 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/schema_main.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/schema_main.json @@ -1,23 +1,17 @@ { "key": "system", - "type": "dict-invisible", - "children": [ - { - "type": "dict-invisible", - "key": "global", - "children": [{ - "type": "schema", - "name": "schema_general" - },{ - "type": "schema", - "name": "schema_modules" - }, { - "type": "schema", - "name": "schema_applications" - }, { - "type": "schema", - "name": "schema_tools" - }] - } - ] + "type": "dict", + "children": [{ + "type": "schema", + "name": "schema_general" + }, { + "type": "schema", + "name": "schema_modules" + }, { + "type": "schema", + "name": "schema_applications" + }, { + "type": "schema", + "name": "schema_tools" + }] } diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/schema_modules.json b/pype/tools/settings/settings/gui_schemas/system_schema/schema_modules.json index fa84a27ae3..31eaab2ede 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/schema_modules.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/schema_modules.json @@ -5,286 +5,184 @@ "collapsable": true, "is_file": true, "children": [{ - "type": "dict", - "key": "Avalon", - "label": "Avalon", - "collapsable": true, - "children": [{ - "type": "text", - "key": "AVALON_MONGO", - "label": "Avalon Mongo URL" - }, - { - "type": "text", - "key": "AVALON_DB_DATA", - "label": "Avalon Mongo Data Location" - }, - { - "type": "text", - "key": "AVALON_THUMBNAIL_ROOT", - "label": "Thumbnail Storage Location" - }, - { - "key": "environment", - "label": "Environment", - "type": "raw-json", - "env_group_key": "avalon" - } - ] - }, { - "type": "dict", - "key": "Ftrack", - "label": "Ftrack", - "collapsable": true, - "checkbox_key": "enabled", - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "text", - "key": "ftrack_server", - "label": "Server" - }, - { - "type": "splitter" - }, - { - "type": "label", - "label": "Additional Ftrack paths" - }, - { - "type": "list", - "key": "ftrack_actions_path", - "label": "Action paths", - "object_type": "text" - }, - { - "type": "list", - "key": "ftrack_events_path", - "label": "Event paths", - "object_type": "text" - }, - { - "type": "splitter" - }, - { - "type": "label", - "label": "Ftrack event server advanced settings" - }, - { - "type": "text", - "key": "FTRACK_EVENTS_MONGO_DB", - "label": "Event Mongo DB" - }, - { - "type": "text", - "key": "FTRACK_EVENTS_MONGO_COL", - "label": "Events Mongo Collection" - }, - { - "type": "dict", - "key": "sync_to_avalon", - "label": "Sync to avalon", - "children": [{ - "type": "list", - "key": "statuses_name_change", - "label": "Status name change", - "object_type": { - "type": "text", - "multiline": false - } - }] - }, - { - "type": "dict-modifiable", - "key": "status_version_to_task", - "label": "Version to Task status mapping", - "object_type": "text" - }, - { - "type": "dict-modifiable", - "key": "status_update", - "label": "Status Updates", - "object_type": { - "type": "list", - "object_type": "text" + "type": "dict", + "key": "Avalon", + "label": "Avalon", + "collapsable": true, + "children": [{ + "type": "text", + "key": "AVALON_MONGO", + "label": "Avalon Mongo URL" + }, + { + "type": "text", + "key": "AVALON_DB_DATA", + "label": "Avalon Mongo Data Location" + }, + { + "type": "text", + "key": "AVALON_THUMBNAIL_ROOT", + "label": "Thumbnail Storage Location" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "avalon" } - }, - { - "key": "intent", - "type": "dict-invisible", - "children": [{ - "type": "dict-modifiable", - "object_type": "text", - "key": "items", - "label": "Intent Key/Label" - }, - { - "key": "default", - "type": "text", - "label": "Default Intent" - } - ] - }, - { - "type": "splitter" - }, - { - "key": "environment", - "label": "Environment", - "type": "raw-json", - "env_group_key": "ftrack" - } - ] - }, { - "type": "dict", - "key": "Rest Api", - "label": "Rest Api", - "collapsable": true, - "children": [{ - "type": "number", - "key": "default_port", - "label": "Default Port", - "minimum": 1, - "maximum": 65535 - }, - { - "type": "list", - "key": "exclude_ports", - "label": "Exclude ports", - "object_type": { + ] + }, { + "type": "schema", + "name": "schema_ftrack" + }, + { + "type": "dict", + "key": "Rest Api", + "label": "Rest Api", + "collapsable": true, + "children": [{ "type": "number", + "key": "default_port", + "label": "Default Port", "minimum": 1, "maximum": 65535 + }, + { + "type": "list", + "key": "exclude_ports", + "label": "Exclude ports", + "object_type": { + "type": "number", + "minimum": 1, + "maximum": 65535 + } } - } - ] - }, { - "type": "dict", - "key": "Timers Manager", - "label": "Timers Manager", - "collapsable": true, - "checkbox_key": "enabled", - "children": [{ + ] + }, { + "type": "dict", + "key": "Timers Manager", + "label": "Timers Manager", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "number", + "decimal": 2, + "key": "full_time", + "label": "Max idle time" + }, { + "type": "number", + "decimal": 2, + "key": "message_time", + "label": "When dialog will show" + } + ] + }, { + "type": "dict", + "key": "Clockify", + "label": "Clockify", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "workspace_name", + "label": "Workspace name" + } + ] + }, { + "type": "dict", + "key": "Deadline", + "label": "Deadline", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ "type": "boolean", "key": "enabled", "label": "Enabled" - }, - { - "type": "number", - "decimal": 2, - "key": "full_time", - "label": "Max idle time" }, { - "type": "number", - "decimal": 2, - "key": "message_time", - "label": "When dialog will show" - } - ] - }, { - "type": "dict", - "key": "Clockify", - "label": "Clockify", - "collapsable": true, - "checkbox_key": "enabled", - "children": [{ + "type": "text", + "key": "DEADLINE_REST_URL", + "label": "Deadline Resl URL" + }] + }, { + "type": "dict", + "key": "Muster", + "label": "Muster", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ "type": "boolean", "key": "enabled", "label": "Enabled" - }, - { + }, { "type": "text", - "key": "workspace_name", - "label": "Workspace name" - } - ] - }, { - "type": "dict", - "key": "Deadline", - "label": "Deadline", - "collapsable": true, - "checkbox_key": "enabled", - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "MUSTER_REST_URL", + "label": "Muster Resl URL" + }, { + "type": "dict-modifiable", + "object_type": { + "type": "number", + "minimum": 0, + "maximum": 300 + }, + "is_group": true, + "key": "templates_mapping", + "label": "Templates mapping", + "is_file": true + }] }, { - "type": "text", - "key": "DEADLINE_REST_URL", - "label": "Deadline Resl URL" - }] - }, { - "type": "dict", - "key": "Muster", - "label": "Muster", - "collapsable": true, - "checkbox_key": "enabled", - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" + "type": "dict", + "key": "Logging", + "label": "Logging", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }] }, { - "type": "text", - "key": "MUSTER_REST_URL", - "label": "Muster Resl URL" + "type": "dict", + "key": "User setting", + "label": "User setting", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }] }, { - "type": "dict-modifiable", - "object_type": { - "type": "number", - "minimum": 0, - "maximum": 300 - }, - "is_group": true, - "key": "templates_mapping", - "label": "Templates mapping", - "is_file": true - }] - }, { - "type": "dict", - "key": "Logging", - "label": "Logging", - "collapsable": true, - "checkbox_key": "enabled", - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }] - }, { - "type": "dict", - "key": "User setting", - "label": "User setting", - "collapsable": true, - "checkbox_key": "enabled", - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }] - }, { - "type": "dict", - "key": "Standalone Publish", - "label": "Standalone Publish", - "collapsable": true, - "checkbox_key": "enabled", - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }] - }, { - "type": "dict", - "key": "Idle Manager", - "label": "Idle Manager", - "collapsable": true, - "checkbox_key": "enabled", - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }] - }] + "type": "dict", + "key": "Standalone Publish", + "label": "Standalone Publish", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }] + }, { + "type": "dict", + "key": "Idle Manager", + "label": "Idle Manager", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }] + } + ] } diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/schema_tools.json b/pype/tools/settings/settings/gui_schemas/system_schema/schema_tools.json index 97fbd5c390..c8f4829a09 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/schema_tools.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/schema_tools.json @@ -18,7 +18,7 @@ "name": "schema_yeti" }, { - "type": "dict-invisible", + "type": "dict", "key": "other", "children": [ { diff --git a/pype/tools/settings/settings/style/style.css b/pype/tools/settings/settings/style/style.css index dcc7a5effe..38fcd14448 100644 --- a/pype/tools/settings/settings/style/style.css +++ b/pype/tools/settings/settings/style/style.css @@ -97,6 +97,11 @@ QPushButton[btn-type="tool-item"]:hover { background-color: transparent; } +QPushButton[btn-type="tool-item-icon"] { + border: 0px solid #bfccd6; + background-color: transparent; +} + QPushButton[btn-type="expand-toggle"] { background: #1d272f; } diff --git a/pype/tools/settings/settings/widgets/anatomy_types.py b/pype/tools/settings/settings/widgets/anatomy_types.py index e1a726187c..ad24d54050 100644 --- a/pype/tools/settings/settings/widgets/anatomy_types.py +++ b/pype/tools/settings/settings/widgets/anatomy_types.py @@ -1,7 +1,11 @@ from Qt import QtWidgets, QtCore from .widgets import ExpandingWidget from .item_types import ( - SettingObject, ModifiableDict, PathWidget, RawJsonWidget + SettingObject, + ModifiableDict, + PathWidget, + RawJsonWidget, + DictWidget ) from .lib import NOT_SET, TypeToKlass, CHILD_OFFSET, METADATA_KEY @@ -33,9 +37,7 @@ class AnatomyWidget(QtWidgets.QWidget, SettingObject): "representation": "png" } - def __init__( - self, input_data, parent, as_widget=False, label_widget=None - ): + def __init__(self, schema_data, parent, as_widget=False): if as_widget: raise TypeError( "`AnatomyWidget` does not allow to be used as widget." @@ -43,21 +45,18 @@ class AnatomyWidget(QtWidgets.QWidget, SettingObject): super(AnatomyWidget, self).__init__(parent) self.setObjectName("AnatomyWidget") - self.initial_attributes(input_data, parent, as_widget) + self.initial_attributes(schema_data, parent, as_widget) - self.key = input_data["key"] + self.input_fields = [] - children_data = input_data["children"] - roots_input_data = {} - templates_input_data = {} - for child in children_data: - if child["type"] == "anatomy_roots": - roots_input_data = child - elif child["type"] == "anatomy_templates": - templates_input_data = child + self.key = schema_data["key"] - self.root_widget = RootsWidget(roots_input_data, self) - self.templates_widget = TemplatesWidget(templates_input_data, self) + def create_ui(self, label_widget=None): + children_data = self.schema_data["children"] + for schema_data in children_data: + item = TypeToKlass.types[schema_data["type"]](schema_data, self) + item.create_ui() + self.input_fields.append(item) self.setAttribute(QtCore.Qt.WA_StyledBackground) @@ -73,17 +72,15 @@ class AnatomyWidget(QtWidgets.QWidget, SettingObject): content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0) content_layout.setSpacing(5) - content_layout.addWidget(self.root_widget) - content_layout.addWidget(self.templates_widget) + for input_field in self.input_fields: + content_layout.addWidget(input_field) + input_field.value_changed.connect(self._on_value_change) body_widget.set_content_widget(content_widget) self.body_widget = body_widget self.label_widget = body_widget.label_widget - self.root_widget.value_changed.connect(self._on_value_change) - self.templates_widget.value_changed.connect(self._on_value_change) - def update_default_values(self, parent_values): self._state = None self._child_state = None @@ -93,8 +90,8 @@ class AnatomyWidget(QtWidgets.QWidget, SettingObject): else: value = NOT_SET - self.root_widget.update_default_values(value) - self.templates_widget.update_default_values(value) + for input_field in self.input_fields: + input_field.update_default_values(value) def update_studio_values(self, parent_values): self._state = None @@ -105,8 +102,8 @@ class AnatomyWidget(QtWidgets.QWidget, SettingObject): else: value = NOT_SET - self.root_widget.update_studio_values(value) - self.templates_widget.update_studio_values(value) + for input_field in self.input_fields: + input_field.update_studio_values(value) def apply_overrides(self, parent_values): # Make sure this is set to False @@ -117,8 +114,8 @@ class AnatomyWidget(QtWidgets.QWidget, SettingObject): if parent_values is not NOT_SET: value = parent_values.get(self.key, value) - self.root_widget.apply_overrides(value) - self.templates_widget.apply_overrides(value) + for input_field in self.input_fields: + input_field.apply_overrides(value) def set_value(self, value): raise TypeError("AnatomyWidget does not allow to use `set_value`") @@ -152,57 +149,58 @@ class AnatomyWidget(QtWidgets.QWidget, SettingObject): self._child_state = child_state def hierarchical_style_update(self): - self.root_widget.hierarchical_style_update() - self.templates_widget.hierarchical_style_update() + for input_field in self.input_fields: + input_field.hierarchical_style_update() + self.update_style() @property def child_has_studio_override(self): - return ( - self.root_widget.child_has_studio_override - or self.templates_widget.child_has_studio_override - ) + for input_field in self.input_fields: + if input_field.child_has_studio_override: + return True + return False @property def child_modified(self): - return ( - self.root_widget.child_modified - or self.templates_widget.child_modified - ) + for input_field in self.input_fields: + if input_field.child_modified: + return True + return False @property def child_overriden(self): - return ( - self.root_widget.child_overriden - or self.templates_widget.child_overriden - ) + for input_field in self.input_fields: + if input_field.child_overriden: + return True + return False @property def child_invalid(self): - return ( - self.root_widget.child_invalid - or self.templates_widget.child_invalid - ) + for input_field in self.input_fields: + if input_field.child_invalid: + return True + return False def set_as_overriden(self): - self.root_widget.set_as_overriden() - self.templates_widget.set_as_overriden() + for input_field in self.input_fields: + input_field.child_invalid.set_as_overriden() def remove_overrides(self): - self.root_widget.remove_overrides() - self.templates_widget.remove_overrides() + for input_field in self.input_fields: + input_field.remove_overrides() def reset_to_pype_default(self): - self.root_widget.reset_to_pype_default() - self.templates_widget.reset_to_pype_default() + for input_field in self.input_fields: + input_field.reset_to_pype_default() def set_studio_default(self): - self.root_widget.set_studio_default() - self.templates_widget.set_studio_default() + for input_field in self.input_fields: + input_field.set_studio_default() def discard_changes(self): - self.root_widget.discard_changes() - self.templates_widget.discard_changes() + for input_field in self.input_fields: + input_field.discard_changes() def overrides(self): if self.child_overriden: @@ -211,20 +209,30 @@ class AnatomyWidget(QtWidgets.QWidget, SettingObject): def item_value(self): output = {} - output.update(self.root_widget.config_value()) - output.update(self.templates_widget.config_value()) + for input_field in self.input_fields: + output.update(input_field.config_value()) return output def studio_overrides(self): - if ( - self.root_widget.child_has_studio_override - or self.templates_widget.child_has_studio_override - ): - groups = [self.root_widget.key, self.templates_widget.key] - value = self.config_value() - value[self.key][METADATA_KEY] = {"groups": groups} - return value, True - return NOT_SET, False + has_overrides = False + for input_field in self.input_fields: + if input_field.child_has_studio_override: + has_overrides = True + break + + if not has_overrides: + return NOT_SET, False + + groups = [] + for input_field in self.input_fields: + groups.append(input_field.key) + + value = self.config_value() + if METADATA_KEY not in value[self.key]: + value[self.key][METADATA_KEY] = {} + value[self.key][METADATA_KEY]["groups"] = groups + + return value, True def config_value(self): return {self.key: self.item_value()} @@ -247,6 +255,7 @@ class RootsWidget(QtWidgets.QWidget, SettingObject): self.studio_is_multiroot = False self.was_multiroot = NOT_SET + def create_ui(self, _label_widget=None): checkbox_widget = QtWidgets.QWidget(self) multiroot_label = QtWidgets.QLabel( "Use multiple roots", checkbox_widget @@ -269,6 +278,7 @@ class RootsWidget(QtWidgets.QWidget, SettingObject): path_widget_data, self, as_widget=True, parent_widget=content_widget ) + singleroot_widget.create_ui() multiroot_data = { "key": self.key, "expandable": False, @@ -281,6 +291,7 @@ class RootsWidget(QtWidgets.QWidget, SettingObject): multiroot_data, self, as_widget=True, parent_widget=content_widget ) + multiroot_widget.create_ui() content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(0, 0, 0, 0) @@ -628,6 +639,7 @@ class TemplatesWidget(QtWidgets.QWidget, SettingObject): self.key = input_data["key"] + def create_ui(self, label_widget=None): body_widget = ExpandingWidget("Templates", self) content_widget = QtWidgets.QWidget(body_widget) body_widget.set_content_widget(content_widget) @@ -638,10 +650,9 @@ class TemplatesWidget(QtWidgets.QWidget, SettingObject): } self.body_widget = body_widget self.label_widget = body_widget.label_widget - self.value_input = RawJsonWidget( - template_input_data, self, - label_widget=self.label_widget - ) + self.value_input = RawJsonWidget(template_input_data, self) + self.value_input.create_ui(label_widget=self.label_widget) + content_layout.addWidget(self.value_input) layout = QtWidgets.QVBoxLayout(self) @@ -754,5 +765,5 @@ class TemplatesWidget(QtWidgets.QWidget, SettingObject): TypeToKlass.types["anatomy"] = AnatomyWidget -TypeToKlass.types["anatomy_roots"] = AnatomyWidget -TypeToKlass.types["anatomy_templates"] = AnatomyWidget +TypeToKlass.types["anatomy_roots"] = RootsWidget +TypeToKlass.types["anatomy_templates"] = TemplatesWidget diff --git a/pype/tools/settings/settings/widgets/base.py b/pype/tools/settings/settings/widgets/base.py index 9b72d99af0..404ef8fed7 100644 --- a/pype/tools/settings/settings/widgets/base.py +++ b/pype/tools/settings/settings/widgets/base.py @@ -10,17 +10,18 @@ from pype.settings.lib import ( DEFAULTS_DIR, reset_default_settings, - default_settings, + get_default_settings, - studio_system_settings, - studio_project_settings, - studio_project_anatomy, + get_studio_system_settings_overrides, + get_studio_project_settings_overrides, + get_studio_project_anatomy_overrides, - project_settings_overrides, - project_anatomy_overrides, + get_project_settings_overrides, + get_project_anatomy_overrides, - path_to_project_overrides, - path_to_project_anatomy, + save_studio_settings, + save_project_settings, + save_project_anatomy, apply_overrides, find_environments, @@ -32,25 +33,44 @@ from avalon import io from avalon.vendor import qtawesome -class SystemWidget(QtWidgets.QWidget): - is_overidable = False - has_studio_override = _has_studio_override = False - is_overriden = _is_overriden = False - as_widget = _as_widget = False - any_parent_as_widget = _any_parent_as_widget = False - is_group = _is_group = False - any_parent_is_group = _any_parent_is_group = False +class SettingsCategoryWidget(QtWidgets.QWidget): + schema_category = None + initial_schema_name = None def __init__(self, user_role, parent=None): - super(SystemWidget, self).__init__(parent) + super(SettingsCategoryWidget, self).__init__(parent) self.user_role = user_role + + self.initialize_attributes() + self.create_ui() + self.reset() + + def initialize_attributes(self): self._hide_studio_overrides = False self._ignore_value_changes = False + self.keys = [] self.input_fields = [] - self.environ_fields = [] + self.schema = None + self.main_schema_key = None + # Required attributes for items + self.is_overidable = False + self._has_studio_override = False + self._is_overriden = False + self._as_widget = False + self._is_group = False + self._any_parent_as_widget = False + self._any_parent_is_group = False + self.has_studio_override = self._has_studio_override + self.is_overriden = self._is_overriden + self.as_widget = self._as_widget + self.is_group = self._as_widget + self.any_parent_as_widget = self._any_parent_as_widget + self.any_parent_is_group = self._any_parent_is_group + + def create_ui(self): scroll_widget = QtWidgets.QScrollArea(self) scroll_widget.setObjectName("GroupWidget") content_widget = QtWidgets.QWidget(scroll_widget) @@ -58,63 +78,93 @@ class SystemWidget(QtWidgets.QWidget): content_layout.setContentsMargins(3, 3, 3, 3) content_layout.setSpacing(0) content_layout.setAlignment(QtCore.Qt.AlignTop) - content_widget.setLayout(content_layout) scroll_widget.setWidgetResizable(True) scroll_widget.setWidget(content_widget) - self.scroll_widget = scroll_widget - self.content_layout = content_layout - self.content_widget = content_widget - footer_widget = QtWidgets.QWidget() footer_layout = QtWidgets.QHBoxLayout(footer_widget) if self.user_role == "developer": - save_as_default_btn = QtWidgets.QPushButton("Save as Default") - save_as_default_btn.clicked.connect(self._save_as_defaults) - - refresh_icon = qtawesome.icon("fa.refresh", color="white") - refresh_button = QtWidgets.QPushButton() - refresh_button.setIcon(refresh_icon) - refresh_button.clicked.connect(self._on_refresh) - - hide_studio_overrides = QtWidgets.QCheckBox() - hide_studio_overrides.setChecked(self._hide_studio_overrides) - hide_studio_overrides.stateChanged.connect( - self._on_hide_studio_overrides - ) - - hide_studio_overrides_widget = QtWidgets.QWidget() - hide_studio_overrides_layout = QtWidgets.QHBoxLayout( - hide_studio_overrides_widget - ) - _label_widget = QtWidgets.QLabel( - "Hide studio overrides", hide_studio_overrides_widget - ) - hide_studio_overrides_layout.addWidget(_label_widget) - hide_studio_overrides_layout.addWidget(hide_studio_overrides) - - footer_layout.addWidget(save_as_default_btn, 0) - footer_layout.addWidget(refresh_button, 0) - footer_layout.addWidget(hide_studio_overrides_widget, 0) + self._add_developer_ui(footer_layout) save_btn = QtWidgets.QPushButton("Save") spacer_widget = QtWidgets.QWidget() footer_layout.addWidget(spacer_widget, 1) footer_layout.addWidget(save_btn, 0) - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - self.setLayout(layout) + configurations_widget = QtWidgets.QWidget() + configurations_layout = QtWidgets.QVBoxLayout(configurations_widget) + configurations_layout.setContentsMargins(0, 0, 0, 0) + configurations_layout.setSpacing(0) - layout.addWidget(scroll_widget, 1) - layout.addWidget(footer_widget, 0) + configurations_layout.addWidget(scroll_widget, 1) + configurations_layout.addWidget(footer_widget, 0) + + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + main_layout.addWidget(configurations_widget, 1) save_btn.clicked.connect(self._save) - self.reset() + self.scroll_widget = scroll_widget + self.content_layout = content_layout + self.content_widget = content_widget + self.configurations_widget = configurations_widget + self.main_layout = main_layout + + self.ui_tweaks() + + def ui_tweaks(self): + return + + def _add_developer_ui(self, footer_layout): + save_as_default_btn = QtWidgets.QPushButton("Save as Default") + + refresh_icon = qtawesome.icon("fa.refresh", color="white") + refresh_button = QtWidgets.QPushButton() + refresh_button.setIcon(refresh_icon) + + hide_studio_overrides = QtWidgets.QCheckBox() + hide_studio_overrides.setChecked(self._hide_studio_overrides) + + hide_studio_overrides_widget = QtWidgets.QWidget() + hide_studio_overrides_layout = QtWidgets.QHBoxLayout( + hide_studio_overrides_widget + ) + _label_widget = QtWidgets.QLabel( + "Hide studio overrides", hide_studio_overrides_widget + ) + hide_studio_overrides_layout.addWidget(_label_widget) + hide_studio_overrides_layout.addWidget(hide_studio_overrides) + + footer_layout.addWidget(save_as_default_btn, 0) + footer_layout.addWidget(refresh_button, 0) + footer_layout.addWidget(hide_studio_overrides_widget, 0) + + save_as_default_btn.clicked.connect(self._save_as_defaults) + refresh_button.clicked.connect(self._on_refresh) + hide_studio_overrides.stateChanged.connect( + self._on_hide_studio_overrides + ) + + def save(self): + """Save procedure.""" + raise NotImplementedError("Method `save` is not implemented.") + + def defaults_dir(self): + """Path to defaults folder.""" + raise NotImplementedError("Method `defaults_dir` is not implemented.") + + def update_values(self): + """Procedure of update values of items on context change or reset.""" + raise NotImplementedError("Method `update_values` is not implemented.") + + def validate_defaults_to_save(self, value): + raise NotImplementedError( + "Method `validate_defaults_to_save` not implemented." + ) def any_parent_overriden(self): return False @@ -133,21 +183,22 @@ class SystemWidget(QtWidgets.QWidget): for input_field in self.input_fields: input_field.hierarchical_style_update() - def add_environ_field(self, input_field): - self.environ_fields.append(input_field) - def reset(self): reset_default_settings() + self.keys.clear() self.input_fields.clear() - self.environ_fields.clear() while self.content_layout.count() != 0: widget = self.content_layout.itemAt(0).widget() self.content_layout.removeWidget(widget) widget.deleteLater() - self.schema = lib.gui_schema("system_schema", "schema_main") - self.keys = self.schema.get("keys", []) + self.schema = lib.gui_schema( + self.schema_category, self.initial_schema_name + ) + + self.main_schema_key = self.schema["key"] + self.add_children_gui(self.schema) self._update_values() self.hierarchical_style_update() @@ -179,10 +230,97 @@ class SystemWidget(QtWidgets.QWidget): first_invalid_item.setFocus(True) return False + def _save(self): + if not self.items_are_valid(): + return + + self.save() + + self._update_values() + + def _on_refresh(self): + self.reset() + + def _on_hide_studio_overrides(self, state): + self._hide_studio_overrides = (state == QtCore.Qt.Checked) + self._update_values() + + def _save_as_defaults(self): + if not self.items_are_valid(): + return + + all_values = {} + for item in self.input_fields: + all_values.update(item.config_value()) + + for key in reversed(self.keys): + all_values = {key: all_values} + + # Skip first key and convert data to store + all_values = lib.convert_gui_data_with_metadata( + all_values[self.main_schema_key] + ) + + if not self.validate_defaults_to_save(all_values): + return + + defaults_dir = self.defaults_dir() + keys_to_file = lib.file_keys_from_schema(self.schema) + for key_sequence in keys_to_file: + # Skip first key + key_sequence = key_sequence[1:] + subpath = "/".join(key_sequence) + ".json" + + new_values = all_values + for key in key_sequence: + new_values = new_values[key] + + output_path = os.path.join(defaults_dir, subpath) + dirpath = os.path.dirname(output_path) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + print("Saving data to: ", subpath) + with open(output_path, "w") as file_stream: + json.dump(new_values, file_stream, indent=4) + + reset_default_settings() + + self._update_values() + + def _update_values(self): + self.ignore_value_changes = True + self.update_values() + self.ignore_value_changes = False + + def add_children_gui(self, child_configuration): + klass = lib.TypeToKlass.types.get(child_configuration["type"]) + item = klass(child_configuration, self) + item.create_ui() + self.input_fields.append(item) + self.content_layout.addWidget(item, 0) + + # Add spacer to stretch children guis + self.content_layout.addWidget( + QtWidgets.QWidget(self.content_widget), 1 + ) + + +class SystemWidget(SettingsCategoryWidget): + schema_category = "system_schema" + initial_schema_name = "schema_main" + + def initialize_attributes(self): + self.environ_fields = [] + super(SystemWidget, self).initialize_attributes() + + def add_environ_field(self, input_field): + self.environ_fields.append(input_field) + def duplicated_env_group_validation(self, values=None, overrides=None): try: if overrides is not None: - default_values = default_settings()[SYSTEM_SETTINGS_KEY] + default_values = get_default_settings()[SYSTEM_SETTINGS_KEY] values = apply_overrides(default_values, overrides) else: values = copy.deepcopy(values) @@ -207,93 +345,35 @@ class SystemWidget(QtWidgets.QWidget): return False return True - def _save(self): - if not self.items_are_valid(): - return + def defaults_dir(self): + return os.path.join(DEFAULTS_DIR, SYSTEM_SETTINGS_KEY) + def validate_defaults_to_save(self, values): + return self.duplicated_env_group_validation(values) + + def reset(self): + self.environ_fields.clear() + super(SystemWidget, self).reset() + + def save(self): _data = {} for input_field in self.input_fields: value, _is_group = input_field.studio_overrides() if value is not lib.NOT_SET: _data.update(value) - values = lib.convert_gui_data_to_overrides(_data.get("system", {})) + values = lib.convert_gui_data_to_overrides( + _data.get(self.main_schema_key, {}) + ) if not self.duplicated_env_group_validation(overrides=values): return - dirpath = os.path.dirname(SYSTEM_SETTINGS_PATH) - if not os.path.exists(dirpath): - os.makedirs(dirpath) - - self._update_values() - - def _on_refresh(self): - self.reset() - - def _on_hide_studio_overrides(self, state): - self._hide_studio_overrides = (state == QtCore.Qt.Checked) - self._update_values() - self.hierarchical_style_update() - - def _save_as_defaults(self): - if not self.items_are_valid(): - return - - output = {} - for item in self.input_fields: - output.update(item.config_value()) - - for key in reversed(self.keys): - _output = {key: output} - output = _output - - all_values = {} - for item in self.input_fields: - all_values.update(item.config_value()) - - for key in reversed(self.keys): - _all_values = {key: all_values} - all_values = _all_values - - # Skip first key - all_values = lib.convert_gui_data_with_metadata(all_values["system"]) - - if not self.duplicated_env_group_validation(all_values): - return - - prject_defaults_dir = os.path.join( - DEFAULTS_DIR, SYSTEM_SETTINGS_KEY - ) - keys_to_file = lib.file_keys_from_schema(self.schema) - for key_sequence in keys_to_file: - # Skip first key - key_sequence = key_sequence[1:] - subpath = "/".join(key_sequence) + ".json" - - new_values = all_values - for key in key_sequence: - new_values = new_values[key] - - output_path = os.path.join(prject_defaults_dir, subpath) - dirpath = os.path.dirname(output_path) - if not os.path.exists(dirpath): - os.makedirs(dirpath) - - print("Saving data to: ", subpath) - with open(output_path, "w") as file_stream: - json.dump(new_values, file_stream, indent=4) - - reset_default_settings() - - self._update_values() - self.hierarchical_style_update() - - def _update_values(self): - self.ignore_value_changes = True + save_studio_settings(values) + def update_values(self): default_values = lib.convert_data_to_gui_data({ - "system": default_settings()[SYSTEM_SETTINGS_KEY] + self.main_schema_key: get_default_settings()[SYSTEM_SETTINGS_KEY] }) for input_field in self.input_fields: input_field.update_default_values(default_values) @@ -302,25 +382,12 @@ class SystemWidget(QtWidgets.QWidget): system_values = lib.NOT_SET else: system_values = lib.convert_overrides_to_gui_data( - {"system": studio_system_settings()} + {self.main_schema_key: get_studio_system_settings_overrides()} ) for input_field in self.input_fields: input_field.update_studio_values(system_values) - self.ignore_value_changes = False - - def add_children_gui(self, child_configuration): - item_type = child_configuration["type"] - klass = lib.TypeToKlass.types.get(item_type) - item = klass(child_configuration, self) - self.input_fields.append(item) - self.content_layout.addWidget(item, 0) - - # Add spacer to stretch children guis - spacer = QtWidgets.QWidget(self.content_widget) - self.content_layout.addWidget(spacer, 1) - class ProjectListView(QtWidgets.QListView): left_mouse_released_at = QtCore.Signal(QtCore.QModelIndex) @@ -448,144 +515,30 @@ class ProjectListWidget(QtWidgets.QWidget): ) -class ProjectWidget(QtWidgets.QWidget): - has_studio_override = _has_studio_override = False - is_overriden = _is_overriden = False - as_widget = _as_widget = False - any_parent_as_widget = _any_parent_as_widget = False - is_group = _is_group = False - any_parent_is_group = _any_parent_is_group = False +class ProjectWidget(SettingsCategoryWidget): + schema_category = "projects_schema" + initial_schema_name = "schema_main" - def __init__(self, user_role, parent=None): - super(ProjectWidget, self).__init__(parent) - - self.user_role = user_role - self._hide_studio_overrides = False - - self.is_overidable = False - self._ignore_value_changes = False + def initialize_attributes(self): self.project_name = None - self.input_fields = [] - - scroll_widget = QtWidgets.QScrollArea(self) - scroll_widget.setObjectName("GroupWidget") - content_widget = QtWidgets.QWidget(scroll_widget) - content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setContentsMargins(3, 3, 3, 3) - content_layout.setSpacing(0) - content_layout.setAlignment(QtCore.Qt.AlignTop) - content_widget.setLayout(content_layout) - - scroll_widget.setWidgetResizable(True) - scroll_widget.setWidget(content_widget) + super(ProjectWidget, self).initialize_attributes() + def ui_tweaks(self): project_list_widget = ProjectListWidget(self) - content_layout.addWidget(project_list_widget) - footer_widget = QtWidgets.QWidget() - footer_layout = QtWidgets.QHBoxLayout(footer_widget) + self.main_layout.insertWidget(0, project_list_widget, 0) - if self.user_role == "developer": - save_as_default_btn = QtWidgets.QPushButton("Save as Default") - save_as_default_btn.clicked.connect(self._save_as_defaults) - - refresh_icon = qtawesome.icon("fa.refresh", color="white") - refresh_button = QtWidgets.QPushButton() - refresh_button.setIcon(refresh_icon) - refresh_button.clicked.connect(self._on_refresh) - - hide_studio_overrides = QtWidgets.QCheckBox() - hide_studio_overrides.setChecked(self._hide_studio_overrides) - hide_studio_overrides.stateChanged.connect( - self._on_hide_studio_overrides - ) - - hide_studio_overrides_widget = QtWidgets.QWidget() - hide_studio_overrides_layout = QtWidgets.QHBoxLayout( - hide_studio_overrides_widget - ) - _label_widget = QtWidgets.QLabel( - "Hide studio overrides", hide_studio_overrides_widget - ) - hide_studio_overrides_layout.addWidget(_label_widget) - hide_studio_overrides_layout.addWidget(hide_studio_overrides) - - footer_layout.addWidget(save_as_default_btn, 0) - footer_layout.addWidget(refresh_button, 0) - footer_layout.addWidget(hide_studio_overrides_widget, 0) - - save_btn = QtWidgets.QPushButton("Save") - spacer_widget = QtWidgets.QWidget() - footer_layout.addWidget(spacer_widget, 1) - footer_layout.addWidget(save_btn, 0) - - configurations_widget = QtWidgets.QWidget() - configurations_layout = QtWidgets.QVBoxLayout(configurations_widget) - configurations_layout.setContentsMargins(0, 0, 0, 0) - configurations_layout.setSpacing(0) - - configurations_layout.addWidget(scroll_widget, 1) - configurations_layout.addWidget(footer_widget, 0) - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - self.setLayout(layout) - - layout.addWidget(project_list_widget, 0) - layout.addWidget(configurations_widget, 1) - - save_btn.clicked.connect(self._save) project_list_widget.project_changed.connect(self._on_project_change) self.project_list_widget = project_list_widget - self.scroll_widget = scroll_widget - self.content_layout = content_layout - self.content_widget = content_widget - self.reset() + def defaults_dir(self): + return DEFAULTS_DIR - def any_parent_overriden(self): - return False - - @property - def ignore_value_changes(self): - return self._ignore_value_changes - - @ignore_value_changes.setter - def ignore_value_changes(self, value): - self._ignore_value_changes = value - if value is False: - self.hierarchical_style_update() - - def hierarchical_style_update(self): - for input_field in self.input_fields: - input_field.hierarchical_style_update() - - def reset(self): - if self.content_layout.count() != 0: - for widget in self.input_fields: - self.content_layout.removeWidget(widget) - widget.deleteLater() - self.input_fields.clear() - - self.schema = lib.gui_schema("projects_schema", "0_project_gui_schema") - self.keys = self.schema.get("keys", []) - self.add_children_gui(self.schema) - self._update_values() - self.hierarchical_style_update() - - def add_children_gui(self, child_configuration): - item_type = child_configuration["type"] - klass = lib.TypeToKlass.types.get(item_type) - item = klass(child_configuration, self) - self.input_fields.append(item) - self.content_layout.addWidget(item, 0) - - # Add spacer to stretch children guis - spacer = QtWidgets.QWidget(self.content_widget) - self.content_layout.addWidget(spacer, 1) + def validate_defaults_to_save(self, _): + # Projects does not have any specific validations + return True def _on_project_change(self): project_name = self.project_list_widget.project_name() @@ -594,11 +547,11 @@ class ProjectWidget(QtWidgets.QWidget): _project_anatomy = lib.NOT_SET self.is_overidable = False else: - _project_overrides = project_settings_overrides(project_name) - _project_anatomy = project_anatomy_overrides(project_name) + _project_overrides = get_project_settings_overrides(project_name) + _project_anatomy = get_project_anatomy_overrides(project_name) self.is_overidable = True - overrides = {"project": { + overrides = {self.main_schema_key: { PROJECT_SETTINGS_KEY: lib.convert_overrides_to_gui_data( _project_overrides ), @@ -612,107 +565,19 @@ class ProjectWidget(QtWidgets.QWidget): item.apply_overrides(overrides) self.ignore_value_changes = False - def _save_as_defaults(self): - output = {} - for item in self.input_fields: - output.update(item.config_value()) - - for key in reversed(self.keys): - _output = {key: output} - output = _output - - all_values = {} - for item in self.input_fields: - all_values.update(item.config_value()) - - for key in reversed(self.keys): - _all_values = {key: all_values} - all_values = _all_values - - # Skip first key - all_values = all_values["project"] - - keys_to_file = lib.file_keys_from_schema(self.schema) - for key_sequence in keys_to_file: - # Skip first key - key_sequence = key_sequence[1:] - subpath = "/".join(key_sequence) + ".json" - - new_values = all_values - for key in key_sequence: - new_values = new_values[key] - - output_path = os.path.join(DEFAULTS_DIR, subpath) - dirpath = os.path.dirname(output_path) - if not os.path.exists(dirpath): - os.makedirs(dirpath) - - print("Saving data to: ", subpath) - with open(output_path, "w") as file_stream: - json.dump(new_values, file_stream, indent=4) - - reset_default_settings() - - self._update_values() - self.hierarchical_style_update() - - def items_are_valid(self): - has_invalid = False - for item in self.input_fields: - if item.child_invalid: - has_invalid = True - - if not has_invalid: - return True - - invalid_items = [] - for item in self.input_fields: - invalid_items.extend(item.get_invalid()) - msg_box = QtWidgets.QMessageBox( - QtWidgets.QMessageBox.Warning, - "Invalid input", - "There is invalid value in one of inputs." - " Please lead red color and fix them." - ) - msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok) - msg_box.exec_() - - first_invalid_item = invalid_items[0] - self.scroll_widget.ensureWidgetVisible(first_invalid_item) - if first_invalid_item.isVisible(): - first_invalid_item.setFocus(True) - return False - - def _save(self): - if not self.items_are_valid(): - return - - first_invalid_item = invalid_items[0] - self.scroll_widget.ensureWidgetVisible(first_invalid_item) - if first_invalid_item.isVisible(): - first_invalid_item.setFocus(True) - - def _on_refresh(self): - self.reset() - - def _on_hide_studio_overrides(self, state): - self._hide_studio_overrides = (state == QtCore.Qt.Checked) - self._update_values() - self.hierarchical_style_update() - - def _save_overrides(self): - if not self.items_are_valid(): - return - + def save(self): data = {} studio_overrides = bool(self.project_name is None) for item in self.input_fields: - value, _is_group = item.overrides() + if studio_overrides: + value, _is_group = item.studio_overrides() + else: + value, _is_group = item.overrides() if value is not lib.NOT_SET: data.update(value) output_data = lib.convert_gui_data_to_overrides( - data.get("project") or {} + data.get(self.main_schema_key) or {} ) # Saving overrides data @@ -723,18 +588,13 @@ class ProjectWidget(QtWidgets.QWidget): project_anatomy_data = output_data.get(PROJECT_ANATOMY_KEY, {}) save_project_anatomy(self.project_name, project_anatomy_data) - if self.project_name: - # Refill values with overrides + def update_values(self): + if self.project_name is not None: self._on_project_change() - else: - # Update saved values - self._update_values() - - def _update_values(self): - self.ignore_value_changes = True + return default_values = lib.convert_data_to_gui_data( - {"project": default_settings()} + {self.main_schema_key: get_default_settings()} ) for input_field in self.input_fields: input_field.update_default_values(default_values) @@ -742,12 +602,16 @@ class ProjectWidget(QtWidgets.QWidget): if self._hide_studio_overrides: studio_values = lib.NOT_SET else: - studio_values = lib.convert_overrides_to_gui_data({"project": { - PROJECT_SETTINGS_KEY: studio_project_settings(), - PROJECT_ANATOMY_KEY: studio_project_anatomy() - }}) + studio_values = lib.convert_overrides_to_gui_data({ + self.main_schema_key: { + PROJECT_SETTINGS_KEY: ( + get_studio_project_settings_overrides() + ), + PROJECT_ANATOMY_KEY: ( + get_studio_project_anatomy_overrides() + ) + } + }) for input_field in self.input_fields: input_field.update_studio_values(studio_values) - - self.ignore_value_changes = False diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index fd5364ea17..d96fb60cdd 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -2,9 +2,9 @@ import json import collections from Qt import QtWidgets, QtCore, QtGui from .widgets import ( + IconButton, ExpandingWidget, NumberSpinBox, - PathInput, GridLabelWidget, ComboBox, NiceCheckbox @@ -37,6 +37,9 @@ class SettingObject: """Partially abstract class for Setting's item type workflow.""" # `is_input_type` attribute says if has implemented item type methods is_input_type = True + # `is_wrapper_item` attribute says if item will never hold `key` + # information and is just visually different + is_wrapper_item = False # Each input must have implemented default value for development # when defaults are not filled yet. default_input_value = NOT_SET @@ -134,7 +137,7 @@ class SettingObject: role_name = self.user_role return role_name in self._roles - def initial_attributes(self, input_data, parent, as_widget): + def initial_attributes(self, schema_data, parent, as_widget): """Prepare attributes based on entered arguments. This method should be same for each item type. Few item types @@ -142,23 +145,25 @@ class SettingObject: """ self._set_default_attributes() + self.schema_data = schema_data + self._parent = parent self._as_widget = as_widget - self._roles = input_data.get("roles") + self._roles = schema_data.get("roles") if self._roles is not None and not isinstance(self._roles, list): self._roles = [self._roles] - self._is_group = input_data.get("is_group", False) - self._env_group_key = input_data.get("env_group_key") + self._is_group = schema_data.get("is_group", False) + self._env_group_key = schema_data.get("env_group_key") # TODO not implemented yet - self._is_nullable = input_data.get("is_nullable", False) + self._is_nullable = schema_data.get("is_nullable", False) if self.is_environ: if not self.allow_to_environment: raise TypeError(( "Item {} does not allow to store environment values" - ).format(input_data["type"])) + ).format(schema_data["type"])) self.add_environ_field(self) @@ -178,6 +183,19 @@ class SettingObject: self.hide() self.hidden_by_role = True + if ( + self.is_input_type + and not self.as_widget + and not self.is_wrapper_item + ): + if "key" not in self.schema_data: + error_msg = "Missing \"key\" in schema data. {}".format( + str(schema_data).replace("'", '"') + ) + raise KeyError(error_msg) + + self.key = self.schema_data["key"] + @property def user_role(self): """Tool is running with any user role. @@ -308,9 +326,11 @@ class SettingObject: return True if self.is_overidable: + if self.as_widget: + return self._was_overriden != self.is_overriden return self.was_overriden != self.is_overriden - else: - return self.has_studio_override != self.had_studio_override + + return self.has_studio_override != self.had_studio_override @property def is_overriden(self): @@ -911,7 +931,7 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): def __init__( self, input_data, parent, - as_widget=False, label_widget=None, parent_widget=None + as_widget=False, parent_widget=None ): if parent_widget is None: parent_widget = parent @@ -919,17 +939,16 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): self.initial_attributes(input_data, parent, as_widget) + def create_ui(self, label_widget=None): layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - if not self.as_widget: - self.key = input_data["key"] - if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) - layout.addWidget(label_widget, 0) + if not self.as_widget and not label_widget: + label = self.schema_data["label"] + label_widget = QtWidgets.QLabel(label) + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + layout.addWidget(label_widget, 0) self.label_widget = label_widget checkbox_height = self.style().pixelMetric( @@ -964,34 +983,32 @@ class NumberWidget(QtWidgets.QWidget, InputObject): valid_value_types = (int, float) def __init__( - self, input_data, parent, - as_widget=False, label_widget=None, parent_widget=None + self, schema_data, parent, as_widget=False, parent_widget=None ): if parent_widget is None: parent_widget = parent super(NumberWidget, self).__init__(parent_widget) - self.initial_attributes(input_data, parent, as_widget) + self.initial_attributes(schema_data, parent, as_widget) + def create_ui(self, label_widget=None): layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) kwargs = { - modifier: input_data.get(modifier) + modifier: self.schema_data.get(modifier) for modifier in self.input_modifiers - if input_data.get(modifier) + if self.schema_data.get(modifier) } self.input_field = NumberSpinBox(self, **kwargs) self.setFocusProxy(self.input_field) - if not self._as_widget: - self.key = input_data["key"] - if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget, 0) + if not self._as_widget and not label_widget: + label = self.schema_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0) self.label_widget = label_widget layout.addWidget(self.input_field, 1) @@ -1012,17 +1029,17 @@ class TextWidget(QtWidgets.QWidget, InputObject): valid_value_types = (str, ) def __init__( - self, input_data, parent, - as_widget=False, label_widget=None, parent_widget=None + self, schema_data, parent, as_widget=False, parent_widget=None ): if parent_widget is None: parent_widget = parent super(TextWidget, self).__init__(parent_widget) - self.initial_attributes(input_data, parent, as_widget) - self.multiline = input_data.get("multiline", False) - placeholder = input_data.get("placeholder") + self.initial_attributes(schema_data, parent, as_widget) + self.multiline = schema_data.get("multiline", False) + self.placeholder_text = schema_data.get("placeholder") + def create_ui(self, label_widget=None): layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) @@ -1032,8 +1049,8 @@ class TextWidget(QtWidgets.QWidget, InputObject): else: self.input_field = QtWidgets.QLineEdit(self) - if placeholder: - self.input_field.setPlaceholderText(placeholder) + if self.placeholder_text: + self.input_field.setPlaceholderText(self.placeholder_text) self.setFocusProxy(self.input_field) @@ -1041,12 +1058,10 @@ class TextWidget(QtWidgets.QWidget, InputObject): if self.multiline: layout_kwargs["alignment"] = QtCore.Qt.AlignTop - if not self._as_widget: - self.key = input_data["key"] - if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget, 0, **layout_kwargs) + if not self._as_widget and not label_widget: + label = self.schema_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0, **layout_kwargs) self.label_widget = label_widget layout.addWidget(self.input_field, 1, **layout_kwargs) @@ -1070,46 +1085,61 @@ class TextWidget(QtWidgets.QWidget, InputObject): class PathInputWidget(QtWidgets.QWidget, InputObject): default_input_value = "" value_changed = QtCore.Signal(object) - valid_value_types = (str, ) + valid_value_types = (str, list) def __init__( - self, input_data, parent, - as_widget=False, label_widget=None, parent_widget=None + self, schema_data, parent, as_widget=False, parent_widget=None ): if parent_widget is None: parent_widget = parent super(PathInputWidget, self).__init__(parent_widget) - self.initial_attributes(input_data, parent, as_widget) + self.initial_attributes(schema_data, parent, as_widget) + self.with_arguments = schema_data.get("with_arguments", False) + + def create_ui(self, label_widget=None): layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - if not self._as_widget: - self.key = input_data["key"] - if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget, 0) + if not self._as_widget and not label_widget: + label = self.schema_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0) self.label_widget = label_widget - self.input_field = PathInput(self) - self.setFocusProxy(self.input_field) - layout.addWidget(self.input_field, 1) + self.input_field = QtWidgets.QLineEdit(self) + self.args_input_field = None + if self.with_arguments: + self.input_field.setPlaceholderText("Executable path") + self.args_input_field = QtWidgets.QLineEdit(self) + self.args_input_field.setPlaceholderText("Arguments") + self.setFocusProxy(self.input_field) + layout.addWidget(self.input_field, 8) self.input_field.textChanged.connect(self._on_value_change) + if self.args_input_field: + layout.addWidget(self.args_input_field, 2) + self.args_input_field.textChanged.connect(self._on_value_change) + def set_value(self, value): self.validate_value(value) - self.input_field.setText(value) - def focusOutEvent(self, event): - self.input_field.clear_end_path() - super(PathInput, self).focusOutEvent(event) + if not isinstance(value, list): + self.input_field.setText(value) + elif self.with_arguments: + self.input_field.setText(value[0]) + self.args_input_field.setText(value[1]) + else: + self.input_field.setText(value[0]) def item_value(self): - return self.input_field.text() + path_value = self.input_field.text() + if self.with_arguments: + return [path_value, self.args_input_field.text()] + return path_value class EnumeratorWidget(QtWidgets.QWidget, InputObject): @@ -1117,34 +1147,32 @@ class EnumeratorWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( - self, input_data, parent, - as_widget=False, label_widget=None, parent_widget=None + self, schema_data, parent, as_widget=False, parent_widget=None ): if parent_widget is None: parent_widget = parent super(EnumeratorWidget, self).__init__(parent_widget) - self.initial_attributes(input_data, parent, as_widget) - self.multiselection = input_data.get("multiselection") - self.enum_items = input_data["enum_items"] + self.initial_attributes(schema_data, parent, as_widget) + self.multiselection = schema_data.get("multiselection") + self.enum_items = schema_data["enum_items"] if not self.enum_items: raise ValueError("Attribute `enum_items` is not defined.") + def create_ui(self, label_widget=None): layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - if not self._as_widget: - self.key = input_data["key"] - if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) - layout.addWidget(label_widget, 0) + if not self._as_widget and not label_widget: + label = self.schema_data["label"] + label_widget = QtWidgets.QLabel(label) + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + layout.addWidget(label_widget, 0) self.label_widget = label_widget if self.multiselection: - placeholder = input_data.get("placeholder") + placeholder = self.schema_data.get("placeholder") self.input_field = MultiSelectionComboBox( placeholder=placeholder, parent=self ) @@ -1271,15 +1299,17 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): allow_to_environment = True def __init__( - self, input_data, parent, - as_widget=False, label_widget=None, parent_widget=None + self, schema_data, parent, as_widget=False, parent_widget=None ): if parent_widget is None: parent_widget = parent super(RawJsonWidget, self).__init__(parent_widget) - self.initial_attributes(input_data, parent, as_widget) + self.initial_attributes(schema_data, parent, as_widget) + # By default must be invalid + self._is_invalid = True + def create_ui(self, label_widget=None): layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) @@ -1289,16 +1319,13 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding ) - self._is_invalid = self.input_field.has_invalid_value() self.setFocusProxy(self.input_field) - if not self.as_widget: - self.key = input_data["key"] - if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget, 0, alignment=QtCore.Qt.AlignTop) + if not self.as_widget and not label_widget: + label = self.schema_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0, alignment=QtCore.Qt.AlignTop) self.label_widget = label_widget layout.addWidget(self.input_field, 1, alignment=QtCore.Qt.AlignTop) @@ -1328,18 +1355,23 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): output = {} for key, value in value.items(): output[key.upper()] = value - - if self.is_environ: - output[METADATA_KEY] = { - "environments": { - self.env_group_key: list(output.keys()) - } - } - return output def config_value(self): - return {self.key: self.item_value()} + value = self.item_value() + if self.is_environ: + if METADATA_KEY not in value: + value[METADATA_KEY] = {} + + env_keys = [] + for key in value.keys(): + if key is not METADATA_KEY: + env_keys.append(key) + + value[METADATA_KEY]["environments"] = { + self.env_group_key: env_keys + } + return {self.key: value} class ListItem(QtWidgets.QWidget, SettingObject): @@ -1403,9 +1435,9 @@ class ListItem(QtWidgets.QWidget, SettingObject): self.value_input = ItemKlass( item_schema, self, - as_widget=True, - label_widget=None + as_widget=True ) + self.value_input.create_ui() layout.addWidget(self.value_input, 1) @@ -1535,17 +1567,18 @@ class ListWidget(QtWidgets.QWidget, InputObject): valid_value_types = (list, ) def __init__( - self, input_data, parent, - as_widget=False, label_widget=None, parent_widget=None + self, schema_data, parent, as_widget=False, parent_widget=None ): if parent_widget is None: parent_widget = parent super(ListWidget, self).__init__(parent_widget) self.setObjectName("ListWidget") - self.initial_attributes(input_data, parent, as_widget) + self.initial_attributes(schema_data, parent, as_widget) - object_type = input_data["object_type"] + self.input_fields = [] + + object_type = schema_data["object_type"] if isinstance(object_type, dict): self.item_schema = object_type else: @@ -1553,7 +1586,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): "type": object_type } # Backwards compatibility - input_modifiers = input_data.get("input_modifiers") or {} + input_modifiers = schema_data.get("input_modifiers") or {} if input_modifiers: self.log.warning(( "Used deprecated key `input_modifiers` to define item." @@ -1561,17 +1594,14 @@ class ListWidget(QtWidgets.QWidget, InputObject): )) self.item_schema.update(input_modifiers) - self.input_fields = [] - + def create_ui(self, label_widget=None): layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 5) layout.setSpacing(5) - if not self.as_widget: - self.key = input_data["key"] - if not label_widget: - label_widget = QtWidgets.QLabel(input_data["label"], self) - layout.addWidget(label_widget, alignment=QtCore.Qt.AlignTop) + if not self.as_widget and not label_widget: + label_widget = QtWidgets.QLabel(self.schema_data["label"], self) + layout.addWidget(label_widget, alignment=QtCore.Qt.AlignTop) self.label_widget = label_widget @@ -1716,7 +1746,9 @@ class ListWidget(QtWidgets.QWidget, InputObject): def apply_overrides(self, parent_values): self._is_modified = False - if parent_values is NOT_SET or self.key not in parent_values: + if self.as_widget: + override_value = parent_values + elif parent_values is NOT_SET or self.key not in parent_values: override_value = NOT_SET else: override_value = parent_values[self.key] @@ -1783,39 +1815,39 @@ class ListStrictWidget(QtWidgets.QWidget, InputObject): valid_value_types = (list, ) def __init__( - self, input_data, parent, - as_widget=False, label_widget=None, parent_widget=None + self, schema_data, parent, as_widget=False, parent_widget=None ): if parent_widget is None: parent_widget = parent super(ListStrictWidget, self).__init__(parent_widget) self.setObjectName("ListStrictWidget") - self.initial_attributes(input_data, parent, as_widget) + self.initial_attributes(schema_data, parent, as_widget) + + self.is_horizontal = schema_data.get("horizontal", True) + self.object_types = self.schema_data["object_types"] self.input_fields = [] + def create_ui(self, label_widget=None): layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 5) layout.setSpacing(5) - if not self.as_widget: - self.key = input_data["key"] - if not label_widget: - label_widget = QtWidgets.QLabel(input_data["label"], self) - layout.addWidget(label_widget, alignment=QtCore.Qt.AlignTop) + if not self.as_widget and not label_widget: + label_widget = QtWidgets.QLabel(self.schema_data["label"], self) + layout.addWidget(label_widget, alignment=QtCore.Qt.AlignTop) self.label_widget = label_widget - self._add_children(layout, input_data) + self._add_children(layout) - def _add_children(self, layout, input_data): + def _add_children(self, layout): inputs_widget = QtWidgets.QWidget(self) inputs_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) layout.addWidget(inputs_widget) - horizontal = input_data.get("horizontal", True) - if horizontal: + if self.is_horizontal: inputs_layout = QtWidgets.QHBoxLayout(inputs_widget) else: inputs_layout = QtWidgets.QGridLayout(inputs_widget) @@ -1827,7 +1859,7 @@ class ListStrictWidget(QtWidgets.QWidget, InputObject): self.inputs_layout = inputs_layout children_item_mapping = [] - for child_configuration in input_data["object_types"]: + for child_configuration in self.object_types: item_widget = ListItem( child_configuration, self, self.inputs_widget, is_strict=True ) @@ -1842,7 +1874,7 @@ class ListStrictWidget(QtWidgets.QWidget, InputObject): children_item_mapping.append((label_widget, item_widget)) - if horizontal: + if self.is_horizontal: self._add_children_horizontally(children_item_mapping) else: self._add_children_vertically(children_item_mapping) @@ -1897,8 +1929,6 @@ class ListStrictWidget(QtWidgets.QWidget, InputObject): return self._default_input_value def set_value(self, value): - self.validate_value(value) - if self._is_overriden: method_name = "apply_overrides" elif not self._has_studio_override: @@ -1960,53 +1990,156 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): self._any_parent_is_group = True self._is_empty = False - self.is_key_duplicated = False + self._is_key_duplicated = False + + self._is_required = False + + self.origin_key = NOT_SET + self.origin_key_label = NOT_SET + + if self.collapsable_key: + layout = QtWidgets.QVBoxLayout(self) + else: + layout = QtWidgets.QHBoxLayout(self) - layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(3) ItemKlass = TypeToKlass.types[item_schema["type"]] - - self.key_input = QtWidgets.QLineEdit(self) - self.key_input.setObjectName("DictKey") - - self.value_input = ItemKlass( + value_input = ItemKlass( item_schema, self, - as_widget=True, - label_widget=None + as_widget=True ) - self.add_btn = QtWidgets.QPushButton("+") - self.remove_btn = QtWidgets.QPushButton("-") + value_input.create_ui() - self.add_btn.setFocusPolicy(QtCore.Qt.ClickFocus) - self.remove_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + key_input = QtWidgets.QLineEdit(self) + key_input.setObjectName("DictKey") - self.add_btn.setProperty("btn-type", "tool-item") - self.remove_btn.setProperty("btn-type", "tool-item") + key_label_input = None + wrapper_widget = None + if self.collapsable_key: + key_label_input = QtWidgets.QLineEdit(self) - self.spacer_widget = QtWidgets.QWidget(self) - self.spacer_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) - self.spacer_widget.setVisible(False) + wrapper_widget = ExpandingWidget("", self) + layout.addWidget(wrapper_widget) - layout.addWidget(self.add_btn, 0) - layout.addWidget(self.remove_btn, 0) - layout.addWidget(self.key_input, 0) - layout.addWidget(self.spacer_widget, 1) - layout.addWidget(self.value_input, 1) + content_widget = QtWidgets.QWidget(wrapper_widget) + content_widget.setObjectName("ContentWidget") + content_layout = QtWidgets.QHBoxLayout(content_widget) + content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0) + content_layout.setSpacing(5) - self.setFocusProxy(self.value_input) + wrapper_widget.set_content_widget(content_widget) - self.add_btn.setFixedSize(self._btn_size, self._btn_size) - self.remove_btn.setFixedSize(self._btn_size, self._btn_size) - self.add_btn.clicked.connect(self.on_add_clicked) - self.remove_btn.clicked.connect(self.on_remove_clicked) + content_layout.addWidget(value_input) - self.key_input.textChanged.connect(self._on_key_change) - self.value_input.value_changed.connect(self._on_value_change) + def key_input_focused_out(event): + QtWidgets.QLineEdit.focusOutEvent(key_input, event) + self._on_focus_lose() - self.origin_key = NOT_SET + def key_label_input_focused_out(event): + QtWidgets.QLineEdit.focusOutEvent(key_label_input, event) + self._on_focus_lose() + + key_input.focusOutEvent = key_input_focused_out + key_label_input.focusOutEvent = key_label_input_focused_out + + spacer_widget = None + add_btn = None + if not self.collapsable_key: + spacer_widget = QtWidgets.QWidget(self) + spacer_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + spacer_widget.setVisible(False) + + add_btn = QtWidgets.QPushButton("+") + add_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + add_btn.setProperty("btn-type", "tool-item") + add_btn.setFixedSize(self._btn_size, self._btn_size) + + edit_btn = None + if self.collapsable_key: + edit_btn = IconButton( + "fa.edit", QtCore.Qt.lightGray, QtCore.Qt.white + ) + edit_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + edit_btn.setProperty("btn-type", "tool-item-icon") + edit_btn.setFixedHeight(self._btn_size) + + remove_btn = QtWidgets.QPushButton("-") + remove_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + remove_btn.setProperty("btn-type", "tool-item") + remove_btn.setFixedSize(self._btn_size, self._btn_size) + + key_input_label_widget = None + key_label_input_label_widget = None + if self.collapsable_key: + key_input_label_widget = QtWidgets.QLabel("Key:") + key_label_input_label_widget = QtWidgets.QLabel("Label:") + wrapper_widget.add_widget_before_label(edit_btn) + wrapper_widget.add_widget_after_label(key_input_label_widget) + wrapper_widget.add_widget_after_label(key_input) + wrapper_widget.add_widget_after_label(key_label_input_label_widget) + wrapper_widget.add_widget_after_label(key_label_input) + wrapper_widget.add_widget_after_label(remove_btn) + + else: + layout.addWidget(add_btn, 0) + layout.addWidget(remove_btn, 0) + layout.addWidget(key_input, 0) + layout.addWidget(spacer_widget, 1) + layout.addWidget(value_input, 1) + + self.setFocusProxy(value_input) + + key_input.textChanged.connect(self._on_key_change) + key_input.returnPressed.connect(self._on_enter_press) + if key_label_input: + key_label_input.textChanged.connect(self._on_key_change) + key_label_input.returnPressed.connect(self._on_enter_press) + + value_input.value_changed.connect(self._on_value_change) + if add_btn: + add_btn.clicked.connect(self.on_add_clicked) + if edit_btn: + edit_btn.clicked.connect(self.on_edit_pressed) + remove_btn.clicked.connect(self.on_remove_clicked) + + self.key_input = key_input + self.key_input_label_widget = key_input_label_widget + self.key_label_input = key_label_input + self.key_label_input_label_widget = key_label_input_label_widget + self.value_input = value_input + self.wrapper_widget = wrapper_widget + + self.spacer_widget = spacer_widget + + self.add_btn = add_btn + self.edit_btn = edit_btn + self.remove_btn = remove_btn + + self.set_as_empty(self._is_empty) + + def _style_state(self): + if self.as_widget: + state = self.style_state( + False, + self._is_invalid, + False, + self.is_modified + ) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) + return state + + @property + def collapsable_key(self): + return self._parent.collapsable_key def key_value(self): return self.key_input.text() @@ -2018,32 +2151,99 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): if self.key_value() == "": return True - if self.is_key_duplicated: + if self._is_key_duplicated: return True return False + def set_key_is_duplicated(self, duplicated): + if duplicated == self._is_key_duplicated: + return + + self._is_key_duplicated = duplicated + if self.collapsable_key: + if duplicated: + self.set_edit_mode(True) + else: + self._on_focus_lose() + self.update_style() + + def set_as_required(self, key): + self.key_input.setText(key) + self.key_input.setEnabled(False) + self._is_required = True + + if self._is_empty: + self.set_as_empty(False) + + if self.collapsable_key: + self.remove_btn.setVisible(False) + else: + self.remove_btn.setEnabled(False) + self.add_btn.setEnabled(False) + + def set_as_last_required(self): + if self.add_btn: + self.add_btn.setEnabled(True) + + def _on_focus_lose(self): + if ( + self.edit_btn.hasFocus() + or self.key_input.hasFocus() + or self.key_label_input.hasFocus() + or self.remove_btn.hasFocus() + ): + return + self._on_enter_press() + + def _on_enter_press(self): + if not self.collapsable_key: + return + + if self._is_empty: + self.on_add_clicked() + else: + self.set_edit_mode(False) + + def _on_key_label_change(self): + self.update_key_label() + def _on_key_change(self): if self.value_is_env_group: self.value_input.env_group_key = self.key_input.text() + + self.update_key_label() + self._on_value_change() def _on_value_change(self, item=None): self.update_style() self.value_changed.emit(self) - def update_default_values(self, key, value): + def update_default_values(self, key, label, value): self.origin_key = key self.key_input.setText(key) + if self.key_label_input: + label = label or "" + self.origin_key_label = label + self.key_label_input.setText(label) self.value_input.update_default_values(value) - def update_studio_values(self, key, value): + def update_studio_values(self, key, label, value): self.origin_key = key self.key_input.setText(key) + if self.key_label_input: + label = label or "" + self.origin_key_label = label + self.key_label_input.setText(label) self.value_input.update_studio_values(value) - def apply_overrides(self, key, value): + def apply_overrides(self, key, label, value): self.origin_key = key self.key_input.setText(key) + if self.key_label_input: + label = label or "" + self.origin_key_label = label + self.key_label_input.setText(label) self.value_input.apply_overrides(value) @property @@ -2054,11 +2254,55 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): def is_group(self): return self._parent.is_group - def on_add_clicked(self): - if self._is_empty: - self.set_as_empty(False) + def update_key_label(self): + if not self.wrapper_widget: + return + key_value = self.key_input.text() + key_label_value = self.key_label_input.text() + if key_label_value: + label = "{} ({})".format(key_label_value, key_value) else: - self._parent.add_row(row=self.row() + 1) + label = key_value + self.wrapper_widget.label_widget.setText(label) + + def on_add_clicked(self): + if not self.collapsable_key: + if self._is_empty: + self.set_as_empty(False) + else: + self._parent.add_row(row=self.row() + 1) + return + + if not self._is_empty: + return + + if not self.key_value(): + return + + self.set_as_empty(False) + self._parent.add_row(row=self.row() + 1, is_empty=True) + + def on_edit_pressed(self): + if not self.key_input.isVisible(): + self.set_edit_mode() + else: + self.key_input.setFocus() + + def set_edit_mode(self, enabled=True): + if self.is_invalid and not enabled: + return + self.wrapper_widget.label_widget.setVisible(not enabled) + self.key_label_input_label_widget.setVisible(enabled) + self.key_input.setVisible(enabled) + self.key_input_label_widget.setVisible(enabled) + self.key_label_input.setVisible(enabled) + if not self._is_required: + self.remove_btn.setVisible(enabled) + if enabled: + if self.key_input.isEnabled(): + self.key_input.setFocus() + else: + self.key_label_input.setFocus() def on_remove_clicked(self): self._parent.remove_row(self) @@ -2066,10 +2310,25 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): def set_as_empty(self, is_empty=True): self._is_empty = is_empty - self.key_input.setVisible(not is_empty) self.value_input.setVisible(not is_empty) - self.remove_btn.setEnabled(not is_empty) - self.spacer_widget.setVisible(is_empty) + if not self.collapsable_key: + self.key_input.setVisible(not is_empty) + self.remove_btn.setEnabled(not is_empty) + self.spacer_widget.setVisible(is_empty) + + else: + self.remove_btn.setVisible(False) + self.key_input_label_widget.setVisible(is_empty) + self.key_input.setVisible(is_empty) + self.key_label_input_label_widget.setVisible(is_empty) + self.key_label_input.setVisible(is_empty) + self.edit_btn.setVisible(not is_empty) + + self.wrapper_widget.label_widget.setVisible(not is_empty) + if is_empty: + self.wrapper_widget.hide_toolbox() + else: + self.wrapper_widget.show_toolbox() self._on_value_change() @property @@ -2079,6 +2338,9 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): def is_key_modified(self): return self.key_value() != self.origin_key + def is_key_label_modified(self): + return self.key_label_value() != self.origin_key_label + def is_value_modified(self): return self.value_input.is_modified @@ -2086,7 +2348,11 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): def is_modified(self): if self._is_empty: return False - return self.is_value_modified() or self.is_key_modified() + return ( + self.is_key_modified() + or self.is_key_label_modified() + or self.is_value_modified() + ) def hierarchical_style_update(self): self.value_input.hierarchical_style_update() @@ -2099,19 +2365,50 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): return self.is_key_invalid() or self.value_input.is_invalid def update_style(self): - state = "" + key_input_state = "" if not self._is_empty: if self.is_key_invalid(): - state = "invalid" + key_input_state = "invalid" elif self.is_key_modified(): - state = "modified" + key_input_state = "modified" - self.key_input.setProperty("state", state) + self.key_input.setProperty("state", key_input_state) self.key_input.style().polish(self.key_input) + if not self.wrapper_widget: + return + + state = self._style_state() + + if self._state == state: + return + + self._state = state + + if self.wrapper_widget.label_widget: + self.wrapper_widget.label_widget.setProperty("state", state) + self.wrapper_widget.label_widget.style().polish( + self.wrapper_widget.label_widget + ) + + if state: + child_state = "child-{}".format(state) + else: + child_state = "" + + self.wrapper_widget.side_line_widget.setProperty("state", child_state) + self.wrapper_widget.side_line_widget.style().polish( + self.wrapper_widget.side_line_widget + ) + def row(self): return self._parent.input_fields.index(self) + def key_label_value(self): + if self.collapsable_key: + return self.key_label_input.text() + return NOT_SET + def item_value(self): key = self.key_input.text() value = self.value_input.item_value() @@ -2135,22 +2432,28 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): valid_value_types = (dict, ) def __init__( - self, input_data, parent, - as_widget=False, label_widget=None, parent_widget=None + self, schema_data, parent, as_widget=False, parent_widget=None ): if parent_widget is None: parent_widget = parent super(ModifiableDict, self).__init__(parent_widget) self.setObjectName("ModifiableDict") - self.initial_attributes(input_data, parent, as_widget) + self.initial_attributes(schema_data, parent, as_widget) self.input_fields = [] + self.required_inputs_by_key = {} - self.key = input_data["key"] - self.value_is_env_group = input_data.get("value_is_env_group") or False + # Validation of "key" key + self.key = schema_data["key"] + self.value_is_env_group = ( + schema_data.get("value_is_env_group") or False + ) + self.hightlight_content = schema_data.get("highlight_content") or False + self.collapsable_key = schema_data.get("collapsable_key") or False + self.required_keys = schema_data.get("required_keys") or [] - object_type = input_data["object_type"] + object_type = schema_data["object_type"] if isinstance(object_type, dict): self.item_schema = object_type else: @@ -2158,7 +2461,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.item_schema = { "type": object_type } - input_modifiers = input_data.get("input_modifiers") or {} + input_modifiers = schema_data.get("input_modifiers") or {} if input_modifiers: self.log.warning(( "Used deprecated key `input_modifiers` to define item." @@ -2169,7 +2472,8 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): if self.value_is_env_group: self.item_schema["env_group_key"] = "" - if input_data.get("highlight_content", False): + def create_ui(self, label_widget=None): + if self.hightlight_content: content_state = "hightlighted" bottom_margin = 5 else: @@ -2180,24 +2484,22 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) - if as_widget: + label = self.schema_data.get("label") + + if self.as_widget: body_widget = None self.label_widget = label_widget + + elif label is None: + body_widget = None + self.label_widget = None else: - body_widget = ExpandingWidget(input_data["label"], self) + body_widget = ExpandingWidget(self.schema_data["label"], self) main_layout.addWidget(body_widget) - self.body_widget = body_widget self.label_widget = body_widget.label_widget - collapsable = input_data.get("collapsable", True) - if collapsable: - collapsed = input_data.get("collapsed", True) - if not collapsed: - body_widget.toggle_content() - - else: - body_widget.hide_toolbox(hide_content=False) + self.body_widget = body_widget if body_widget is None: content_parent_widget = self @@ -2208,7 +2510,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): content_widget.setObjectName("ContentWidget") content_widget.setProperty("content_state", content_state) content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setContentsMargins(CHILD_OFFSET, 3, 0, bottom_margin) + content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, bottom_margin) if body_widget is None: main_layout.addWidget(content_widget) @@ -2219,9 +2521,26 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.content_widget = content_widget self.content_layout = content_layout + if body_widget: + collapsable = self.schema_data.get("collapsable", True) + if collapsable: + collapsed = self.schema_data.get("collapsed", True) + if not collapsed: + body_widget.toggle_content() + + else: + body_widget.hide_toolbox(hide_content=False) + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - self.add_row(is_empty=True) + last_required_item = None + for key in self.required_keys: + last_required_item = self.add_row(key=key, is_required=True) + + if last_required_item: + last_required_item.set_as_last_required() + else: + self.add_row(is_empty=True) def count(self): return len(self.input_fields) @@ -2229,9 +2548,24 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): def set_value(self, value): self.validate_value(value) - previous_inputs = tuple(self.input_fields) + metadata = value.get(METADATA_KEY, {}) + dynamic_key_labels = metadata.get("dynamic_key_label") or {} + + required_items = list(self.required_inputs_by_key.values()) + previous_inputs = list() + for input_field in self.input_fields: + if input_field not in required_items: + previous_inputs.append(input_field) + for item_key, item_value in value.items(): - self.add_row(key=item_key, value=item_value) + if item_key is METADATA_KEY: + continue + + label = dynamic_key_labels.get(item_key) + self.add_row(key=item_key, label=label, value=item_value) + + if self.collapsable_key: + self.add_row(is_empty=True) for input_field in previous_inputs: self.remove_row(input_field) @@ -2251,13 +2585,10 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): for fields in fields_by_keys.values(): if len(fields) == 1: field = fields[0] - if field.is_key_duplicated: - field.is_key_duplicated = False - field.update_style() + field.set_key_is_duplicated(False) else: for field in fields: - field.is_key_duplicated = True - field.update_style() + field.set_key_is_duplicated(True) if self.is_overidable: self._is_overriden = True @@ -2324,13 +2655,44 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): output.update(item.item_value()) return output + def item_value_with_metadata(self): + if not self.collapsable_key: + output = self.item_value() + + else: + output = {} + labels_by_key = {} + for item in self.input_fields: + labels_by_key[item.key_value()] = item.key_label_value() + output.update(item.config_value()) + if METADATA_KEY not in output: + output[METADATA_KEY] = {} + output[METADATA_KEY]["dynamic_key_label"] = labels_by_key + + if self.value_is_env_group: + for env_group_key, value in tuple(output.items()): + env_keys = [] + for key in value.keys(): + if key is not METADATA_KEY: + env_keys.append(key) + + if METADATA_KEY not in value: + value[METADATA_KEY] = {} + + value[METADATA_KEY]["environments"] = {env_group_key: env_keys} + output[env_group_key] = value + return output + def item_value(self): output = {} for item in self.input_fields: output.update(item.config_value()) return output - def add_row(self, row=None, key=None, value=None, is_empty=False): + def config_value(self): + return {self.key: self.item_value_with_metadata()} + + def _create_item(self, row, key, is_empty, is_required): # Create new item item_widget = ModifiableDictItem( self.item_schema, self, self.content_widget @@ -2338,6 +2700,10 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): if is_empty: item_widget.set_as_empty() + if is_required: + item_widget.set_as_required(key) + self.required_inputs_by_key[key] = item_widget + item_widget.value_changed.connect(self._on_value_change) if row is None: @@ -2348,30 +2714,55 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.input_fields.insert(row, item_widget) previous_input = None - for input_field in self.input_fields: - if previous_input is not None: + if self.collapsable_key: + for input_field in self.input_fields: + if previous_input is not None: + self.setTabOrder( + previous_input, input_field.value_input + ) + previous_input = input_field.value_input.focusProxy() + + else: + for input_field in self.input_fields: + if previous_input is not None: + self.setTabOrder( + previous_input, input_field.key_input + ) + previous_input = input_field.value_input.focusProxy() self.setTabOrder( - previous_input, input_field.key_input + input_field.key_input, previous_input ) - previous_input = input_field.value_input.focusProxy() - self.setTabOrder( - input_field.key_input, previous_input - ) + return item_widget + + def add_row( + self, + row=None, + key=None, + label=None, + value=None, + is_empty=False, + is_required=False + ): + item_widget = self.required_inputs_by_key.get(key) + if not item_widget: + item_widget = self._create_item(row, key, is_empty, is_required) # Set value if entered value is not None # else (when add button clicked) trigger `_on_value_change` if value is not None and key is not None: if not self._has_studio_override: - item_widget.update_default_values(key, value) + item_widget.update_default_values(key, label, value) elif self._is_overriden: - item_widget.apply_overrides(key, value) + item_widget.apply_overrides(key, label, value) else: - item_widget.update_studio_values(key, value) + item_widget.update_studio_values(key, label, value) self.hierarchical_style_update() else: self._on_value_change() self.parent().updateGeometry() + return item_widget + def remove_row(self, item_widget): item_widget.value_changed.disconnect() @@ -2405,26 +2796,32 @@ class DictWidget(QtWidgets.QWidget, SettingObject): valid_value_types = (dict, type(NOT_SET)) def __init__( - self, input_data, parent, - as_widget=False, label_widget=None, parent_widget=None + self, schema_data, parent, as_widget=False, parent_widget=None ): if parent_widget is None: parent_widget = parent super(DictWidget, self).__init__(parent_widget) - self.initial_attributes(input_data, parent, as_widget) + self.initial_attributes(schema_data, parent, as_widget) self.input_fields = [] self.checkbox_widget = None - self.checkbox_key = input_data.get("checkbox_key") + self.checkbox_key = schema_data.get("checkbox_key") - self.label_widget = label_widget + self.highlight_content = schema_data.get("highlight_content", False) + self.show_borders = schema_data.get("show_borders", True) + self.collapsable = schema_data.get("collapsable", True) + self.collapsed = schema_data.get("collapsed", True) - if self.as_widget: - self._ui_as_widget(input_data) + def create_ui(self, label_widget=None): + if not self.as_widget and self.schema_data.get("label") is None: + self._ui_item_without_label() else: - self._ui_as_item(input_data) + self._ui_item_or_as_widget(label_widget) + + for child_data in self.schema_data.get("children", []): + self.add_children_gui(child_data) any_visible = False for input_field in self.input_fields: @@ -2435,68 +2832,71 @@ class DictWidget(QtWidgets.QWidget, SettingObject): if not any_visible: self.hide() - def _ui_as_item(self, input_data): - self.key = input_data["key"] - if input_data.get("highlight_content", False): - content_state = "hightlighted" - bottom_margin = 5 + def _ui_item_without_label(self): + if self._is_group: + raise TypeError( + "Dictionary without label can't be marked as group input." + ) + + self.setObjectName("DictInvisible") + + self.label_widget = None + self.body_widget = None + self.content_layout = QtWidgets.QGridLayout(self) + self.content_layout.setContentsMargins(0, 0, 0, 0) + self.content_layout.setSpacing(5) + + def _ui_item_or_as_widget(self, label_widget): + content_widget = QtWidgets.QWidget(self) + + if self.as_widget: + content_widget.setObjectName("DictAsWidgetBody") + show_borders = str(int(self.show_borders)) + content_widget.setProperty("show_borders", show_borders) + content_layout_margins = (5, 5, 5, 5) + main_layout_spacing = 5 + body_widget = None + else: - content_state = "" - bottom_margin = 0 + content_widget.setObjectName("ContentWidget") + if self.highlight_content: + content_state = "hightlighted" + bottom_margin = 5 + else: + content_state = "" + bottom_margin = 0 + content_widget.setProperty("content_state", content_state) + content_layout_margins = (CHILD_OFFSET, 5, 0, bottom_margin) + main_layout_spacing = 0 + + body_widget = ExpandingWidget(self.schema_data["label"], self) + label_widget = body_widget.label_widget + body_widget.set_content_widget(content_widget) + + content_layout = QtWidgets.QGridLayout(content_widget) + content_layout.setContentsMargins(*content_layout_margins) main_layout = QtWidgets.QHBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) - main_layout.setSpacing(0) - - body_widget = ExpandingWidget(input_data["label"], self) - - main_layout.addWidget(body_widget) - - content_widget = QtWidgets.QWidget(body_widget) - content_widget.setObjectName("ContentWidget") - content_widget.setProperty("content_state", content_state) - content_layout = QtWidgets.QGridLayout(content_widget) - content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, bottom_margin) - - body_widget.set_content_widget(content_widget) - - self.body_widget = body_widget - self.content_widget = content_widget - self.content_layout = content_layout - - self.label_widget = body_widget.label_widget - - for child_data in input_data.get("children", []): - self.add_children_gui(child_data) - - collapsable = input_data.get("collapsable", True) - if len(self.input_fields) == 1 and self.checkbox_widget: - body_widget.hide_toolbox(hide_content=True) - - elif collapsable: - collapsed = input_data.get("collapsed", True) - if not collapsed: - body_widget.toggle_content() + main_layout.setSpacing(main_layout_spacing) + if not body_widget: + main_layout.addWidget(content_widget) else: - body_widget.hide_toolbox(hide_content=False) + main_layout.addWidget(body_widget) - def _ui_as_widget(self, input_data): - body = QtWidgets.QWidget(self) - body.setObjectName("DictAsWidgetBody") - show_borders = str(int(input_data.get("show_borders", True))) - body.setProperty("show_borders", show_borders) - - content_layout = QtWidgets.QGridLayout(body) - content_layout.setContentsMargins(5, 5, 5, 5) + self.label_widget = label_widget + self.body_widget = body_widget self.content_layout = content_layout - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(5) - layout.addWidget(body) + if body_widget: + if len(self.input_fields) == 1 and self.checkbox_widget: + body_widget.hide_toolbox(hide_content=True) - for child_configuration in input_data["children"]: - self.add_children_gui(child_configuration) + elif self.collapsable: + if not self.collapsed: + body_widget.toggle_content() + else: + body_widget.hide_toolbox(hide_content=False) def add_children_gui(self, child_configuration): item_type = child_configuration["type"] @@ -2516,17 +2916,23 @@ class DictWidget(QtWidgets.QWidget, SettingObject): "SCHEMA BUG: Dictionary item has set as checkbox" " item invalid type \"{}\". Expected \"boolean\"." ).format(child_configuration["type"])) + elif self.body_widget is None: + self.log.warning(( + "SCHEMA BUG: Dictionary item has set checkbox" + " item but item does not have label." + ).format(child_configuration["type"])) else: return self._add_checkbox_child(child_configuration) label_widget = None - if not klass.expand_in_grid: + item = klass(child_configuration, self) + if not item.expand_in_grid: label = child_configuration.get("label") if label is not None: label_widget = GridLabelWidget(label, self) self.content_layout.addWidget(label_widget, row, 0, 1, 1) - item = klass(child_configuration, self, label_widget=label_widget) + item.create_ui(label_widget=label_widget) item.value_changed.connect(self._on_value_change) if label_widget: @@ -2542,8 +2948,9 @@ class DictWidget(QtWidgets.QWidget, SettingObject): def _add_checkbox_child(self, child_configuration): item = BooleanWidget( - child_configuration, self, label_widget=self.label_widget + child_configuration, self ) + item.create_ui(label_widget=self.label_widget) item.value_changed.connect(self._on_value_change) self.body_widget.add_widget_before_label(item) @@ -2706,7 +3113,7 @@ class DictWidget(QtWidgets.QWidget, SettingObject): def update_style(self, is_overriden=None): # TODO add style update when used as widget - if self.as_widget: + if not self.body_widget: return child_has_studio_override = self.child_has_studio_override @@ -2835,299 +3242,6 @@ class DictWidget(QtWidgets.QWidget, SettingObject): return self._override_values(True) -class DictInvisible(QtWidgets.QWidget, SettingObject): - # TODO is not overridable by itself - value_changed = QtCore.Signal(object) - allow_actions = False - expand_in_grid = True - valid_value_types = (dict, type(NOT_SET)) - - def __init__( - self, input_data, parent, - as_widget=False, label_widget=None, parent_widget=None - ): - if parent_widget is None: - parent_widget = parent - super(DictInvisible, self).__init__(parent_widget) - self.setObjectName("DictInvisible") - - self.initial_attributes(input_data, parent, as_widget) - - if self._is_group: - raise TypeError("DictInvisible can't be marked as group input.") - - self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - - layout = QtWidgets.QGridLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(5) - - self.content_layout = layout - - self.input_fields = [] - - self.key = input_data["key"] - - for child_data in input_data.get("children", []): - self.add_children_gui(child_data) - - any_visible = False - for input_field in self.input_fields: - if not input_field.hidden_by_role: - any_visible = True - break - - if not any_visible: - self.hide() - - def add_children_gui(self, child_configuration): - item_type = child_configuration["type"] - klass = TypeToKlass.types.get(item_type) - - row = self.content_layout.rowCount() - if not getattr(klass, "is_input_type", False): - item = klass(child_configuration, self) - self.content_layout.addWidget(item, row, 0, 1, 2) - return item - - label_widget = None - if not klass.expand_in_grid: - label = child_configuration.get("label") - if label is not None: - label_widget = GridLabelWidget(label, self) - self.content_layout.addWidget(label_widget, row, 0, 1, 1) - - item = klass(child_configuration, self, label_widget=label_widget) - item.value_changed.connect(self._on_value_change) - - if label_widget: - if item.hidden_by_role: - label_widget.hide() - label_widget.input_field = item - self.content_layout.addWidget(item, row, 1, 1, 1) - else: - self.content_layout.addWidget(item, row, 0, 1, 2) - - self.input_fields.append(item) - return item - - def update_style(self, *args, **kwargs): - return - - @property - def child_has_studio_override(self): - for input_field in self.input_fields: - if ( - input_field.has_studio_override - or input_field.child_has_studio_override - ): - return True - return False - - @property - def child_modified(self): - for input_field in self.input_fields: - if input_field.child_modified: - return True - return False - - @property - def child_overriden(self): - for input_field in self.input_fields: - if input_field.is_overriden or input_field.child_overriden: - return True - return False - - @property - def child_invalid(self): - for input_field in self.input_fields: - if input_field.child_invalid: - return True - return False - - def get_invalid(self): - output = [] - for input_field in self.input_fields: - output.extend(input_field.get_invalid()) - return output - - def item_value(self): - output = {} - for input_field in self.input_fields: - # TODO maybe merge instead of update should be used - # NOTE merge is custom function which merges 2 dicts - output.update(input_field.config_value()) - return output - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - if self.is_group and not self.any_parent_as_widget: - if self.is_overidable: - self._is_overriden = True - else: - self._has_studio_override = True - self.hierarchical_style_update() - - self.value_changed.emit(self) - - def hierarchical_style_update(self): - for input_field in self.input_fields: - input_field.hierarchical_style_update() - self.update_style() - - def remove_overrides(self): - self._is_overriden = False - self._is_modified = False - for input_field in self.input_fields: - input_field.remove_overrides() - - def reset_to_pype_default(self): - for input_field in self.input_fields: - input_field.reset_to_pype_default() - self._has_studio_override = False - - def set_studio_default(self): - for input_field in self.input_fields: - input_field.set_studio_default() - - if self.is_group: - self._has_studio_override = True - - def discard_changes(self): - self._is_modified = False - self._is_overriden = self._was_overriden - self._has_studio_override = self._had_studio_override - - for input_field in self.input_fields: - input_field.discard_changes() - - self._is_modified = self.child_modified - if not self.is_overidable and self.as_widget: - if self.has_studio_override: - self._is_modified = self.studio_value != self.item_value() - else: - self._is_modified = self.default_value != self.item_value() - - self._state = None - self._is_overriden = self._was_overriden - - def set_as_overriden(self): - if self.is_overriden: - return - - if self.is_group: - self._is_overriden = True - return - - for item in self.input_fields: - item.set_as_overriden() - - def update_default_values(self, parent_values): - value = NOT_SET - if self.as_widget: - value = parent_values - elif parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) - - try: - self.validate_value(value) - except InvalidValueType as exc: - value = NOT_SET - self.log.warning(exc.msg) - - for item in self.input_fields: - item.update_default_values(value) - - def update_studio_values(self, parent_values): - value = NOT_SET - if parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) - - try: - self.validate_value(value) - except InvalidValueType as exc: - value = NOT_SET - self.log.warning(exc.msg) - - for item in self.input_fields: - item.update_studio_values(value) - - def apply_overrides(self, parent_values): - # Make sure this is set to False - self._state = None - self._child_state = None - - metadata = {} - groups = tuple() - override_values = NOT_SET - if parent_values is not NOT_SET: - metadata = parent_values.get(METADATA_KEY) or metadata - groups = metadata.get("groups") or groups - override_values = parent_values.get(self.key, override_values) - - self._is_overriden = self.key in groups - - try: - self.validate_value(override_values) - except InvalidValueType as exc: - override_values = NOT_SET - self.log.warning(exc.msg) - - for item in self.input_fields: - item.apply_overrides(override_values) - - if not self._is_overriden: - self._is_overriden = ( - self.is_group - and self.is_overidable - and self.child_overriden - ) - self._was_overriden = bool(self._is_overriden) - - def _override_values(self, project_overrides): - values = {} - groups = [] - for input_field in self.input_fields: - if project_overrides: - value, is_group = input_field.overrides() - else: - value, is_group = input_field.studio_overrides() - if value is NOT_SET: - continue - - if METADATA_KEY in value and METADATA_KEY in values: - new_metadata = value.pop(METADATA_KEY) - values[METADATA_KEY] = self.merge_metadata( - values[METADATA_KEY], new_metadata - ) - - values.update(value) - if is_group: - groups.extend(value.keys()) - - if groups: - if METADATA_KEY not in values: - values[METADATA_KEY] = {} - values[METADATA_KEY]["groups"] = groups - return {self.key: values}, self.is_group - - def studio_overrides(self): - if ( - not (self.as_widget or self.any_parent_as_widget) - and not self.has_studio_override - and not self.child_has_studio_override - ): - return NOT_SET, False - return self._override_values(False) - - def overrides(self): - if not self.is_overriden and not self.child_overriden: - return NOT_SET, False - return self._override_values(True) - - class PathWidget(QtWidgets.QWidget, SettingObject): value_changed = QtCore.Signal(object) platforms = ("windows", "darwin", "linux") @@ -3138,14 +3252,13 @@ class PathWidget(QtWidgets.QWidget, SettingObject): } def __init__( - self, input_data, parent, - as_widget=False, label_widget=None, parent_widget=None + self, schema_data, parent, as_widget=False, parent_widget=None ): if parent_widget is None: parent_widget = parent super(PathWidget, self).__init__(parent_widget) - self.initial_attributes(input_data, parent, as_widget) + self.initial_attributes(schema_data, parent, as_widget) # This is partial input and dictionary input if not self.any_parent_is_group and not self._as_widget: @@ -3153,22 +3266,21 @@ class PathWidget(QtWidgets.QWidget, SettingObject): else: self._is_group = False - self.multiplatform = input_data.get("multiplatform", False) - self.multipath = input_data.get("multipath", False) + self.multiplatform = schema_data.get("multiplatform", False) + self.multipath = schema_data.get("multipath", False) + self.with_arguments = schema_data.get("with_arguments", False) self.input_field = None + def create_ui(self, label_widget=None): layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - if not self.as_widget: - self.key = input_data["key"] - if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) - layout.addWidget(label_widget, 0, alignment=QtCore.Qt.AlignTop) + if not self.as_widget and not label_widget: + label_widget = QtWidgets.QLabel(self.schema_data["label"]) + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + layout.addWidget(label_widget, 0, alignment=QtCore.Qt.AlignTop) self.label_widget = label_widget self.content_widget = QtWidgets.QWidget(self) @@ -3178,7 +3290,7 @@ class PathWidget(QtWidgets.QWidget, SettingObject): layout.addWidget(self.content_widget) - self.create_gui() + self.create_ui_inputs() @property def default_input_value(self): @@ -3194,13 +3306,15 @@ class PathWidget(QtWidgets.QWidget, SettingObject): } return value_type() - def create_gui(self): + def create_ui_inputs(self): if not self.multiplatform and not self.multipath: - input_data = {"key": self.key} - path_input = PathInputWidget( - input_data, self, - as_widget=True, label_widget=self.label_widget - ) + item_schema = { + "key": self.key, + "with_arguments": self.with_arguments + } + path_input = PathInputWidget(item_schema, self, as_widget=True) + path_input.create_ui(label_widget=self.label_widget) + self.setFocusProxy(path_input) self.content_layout.addWidget(path_input) self.input_field = path_input @@ -3210,12 +3324,13 @@ class PathWidget(QtWidgets.QWidget, SettingObject): if not self.multiplatform: item_schema = { "key": self.key, - "object_type": "path-input" + "object_type": { + "type": "path-input", + "with_arguments": self.with_arguments + } } - input_widget = ListWidget( - item_schema, self, - as_widget=True, label_widget=self.label_widget - ) + input_widget = ListWidget(item_schema, self, as_widget=True) + input_widget.create_ui(label_widget=self.label_widget) self.setFocusProxy(input_widget) self.content_layout.addWidget(input_widget) self.input_field = input_widget @@ -3235,15 +3350,19 @@ class PathWidget(QtWidgets.QWidget, SettingObject): } if self.multipath: child_item["type"] = "list" - child_item["object_type"] = "path-input" + child_item["object_type"] = { + "type": "path-input", + "with_arguments": self.with_arguments + } else: child_item["type"] = "path-input" + child_item["with_arguments"] = self.with_arguments item_schema["children"].append(child_item) - input_widget = DictWidget( - item_schema, self, as_widget=True, label_widget=self.label_widget - ) + input_widget = DictWidget(item_schema, self, as_widget=True) + input_widget.create_ui(label_widget=self.label_widget) + self.content_layout.addWidget(input_widget) self.input_field = input_widget input_widget.value_changed.connect(self._on_value_change) @@ -3474,78 +3593,54 @@ class PathWidget(QtWidgets.QWidget, SettingObject): return value, self.is_group -# Proxy for form layout -class FormLabel(QtWidgets.QLabel): - def __init__(self, *args, **kwargs): - super(FormLabel, self).__init__(*args, **kwargs) - self.item = None - - -class DictFormWidget(QtWidgets.QWidget, SettingObject): +class WrapperItemWidget(QtWidgets.QWidget, SettingObject): value_changed = QtCore.Signal(object) allow_actions = False expand_in_grid = True + is_wrapper_item = True def __init__( - self, input_data, parent, - as_widget=False, label_widget=None, parent_widget=None + self, schema_data, parent, as_widget=False, parent_widget=None ): if parent_widget is None: parent_widget = parent - super(DictFormWidget, self).__init__(parent_widget) - - self.initial_attributes(input_data, parent, as_widget) - - self._as_widget = False - self._is_group = False + super(WrapperItemWidget, self).__init__(parent_widget) self.input_fields = [] - self.content_layout = QtWidgets.QFormLayout(self) - self.content_layout.setContentsMargins(0, 0, 0, 0) - for child_data in input_data.get("children", []): - self.add_children_gui(child_data) + self.initial_attributes(schema_data, parent, as_widget) + + if self.as_widget: + raise TypeError( + "Wrapper items ({}) can't be used as widgets.".format( + self.__class__.__name__ + ) + ) + + if self.is_group: + raise TypeError( + "Wrapper items ({}) can't be used as groups.".format( + self.__class__.__name__ + ) + ) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - any_visible = False - for input_field in self.input_fields: - if not input_field.hidden_by_role: - any_visible = True - break + self.wrapper_initial_attributes(schema_data) - if not any_visible: - self.hide() + def wrapper_initial_attributes(self, schema_data): + """Initialization of attributes for specific wrapper.""" + return - def add_children_gui(self, child_configuration): - item_type = child_configuration["type"] - # Pop label to not be set in child - label = child_configuration["label"] + def create_ui(self, label_widget=None): + """UI implementation.""" + raise NotImplementedError( + "Method `create_ui` not implemented." + ) - klass = TypeToKlass.types.get(item_type) - - label_widget = FormLabel(label, self) - - item = klass(child_configuration, self, label_widget=label_widget) - label_widget.item = item - - if item.hidden_by_role: - label_widget.hide() - - item.value_changed.connect(self._on_value_change) - self.content_layout.addRow(label_widget, item) - self.input_fields.append(item) - return item - - def mouseReleaseEvent(self, event): - if event.button() == QtCore.Qt.RightButton: - position = self.mapFromGlobal(QtGui.QCursor().pos()) - widget = self.childAt(position) - if widget and isinstance(widget, FormLabel): - widget.item.mouseReleaseEvent(event) - event.accept() - return - super(DictFormWidget, self).mouseReleaseEvent(event) + def update_style(self): + """Update items styles.""" + return def apply_overrides(self, parent_values): for item in self.input_fields: @@ -3613,6 +3708,7 @@ class DictFormWidget(QtWidgets.QWidget, SettingObject): self.value_changed.emit(self) if self.any_parent_is_group: self.hierarchical_style_update() + self.update_style() @property def child_has_studio_override(self): @@ -3654,6 +3750,7 @@ class DictFormWidget(QtWidgets.QWidget, SettingObject): def hierarchical_style_update(self): for input_field in self.input_fields: input_field.hierarchical_style_update() + self.update_style() def item_value(self): output = {} @@ -3683,7 +3780,9 @@ class DictFormWidget(QtWidgets.QWidget, SettingObject): if is_group: groups.extend(value.keys()) if groups: - values[METADATA_KEY] = {"groups": groups} + if METADATA_KEY not in values: + values[METADATA_KEY] = {} + values[METADATA_KEY]["groups"] = groups return values, self.is_group def overrides(self): @@ -3699,10 +3798,179 @@ class DictFormWidget(QtWidgets.QWidget, SettingObject): if is_group: groups.extend(value.keys()) if groups: - values[METADATA_KEY] = {"groups": groups} + if METADATA_KEY not in values: + values[METADATA_KEY] = {} + values[METADATA_KEY]["groups"] = groups return values, self.is_group +# Proxy for form layout +class FormLabel(QtWidgets.QLabel): + def __init__(self, input_field, *args, **kwargs): + super(FormLabel, self).__init__(*args, **kwargs) + self.input_field = input_field + + def mouseReleaseEvent(self, event): + if self.input_field: + return self.input_field.show_actions_menu(event) + return super(FormLabel, self).mouseReleaseEvent(event) + + +class FormItemWidget(WrapperItemWidget): + def create_ui(self, label_widget=None): + self.content_layout = QtWidgets.QFormLayout(self) + self.content_layout.setContentsMargins(0, 0, 0, 0) + + for child_data in self.schema_data["children"]: + self.add_children_gui(child_data) + + any_visible = False + for input_field in self.input_fields: + if not input_field.hidden_by_role: + any_visible = True + break + + if not any_visible: + self.hidden_by_role = True + self.hide() + + def add_children_gui(self, child_configuration): + item_type = child_configuration["type"] + # Pop label to not be set in child + label = child_configuration["label"] + + klass = TypeToKlass.types.get(item_type) + + item = klass(child_configuration, self) + + label_widget = FormLabel(item, label, self) + + item.create_ui(label_widget=label_widget) + + if item.hidden_by_role: + label_widget.hide() + + item.value_changed.connect(self._on_value_change) + self.content_layout.addRow(label_widget, item) + self.input_fields.append(item) + return item + + +class CollapsibleWrapperItem(WrapperItemWidget): + def wrapper_initial_attributes(self, schema_data): + self.collapsable = schema_data.get("collapsable", True) + self.collapsed = schema_data.get("collapsed", True) + + def create_ui(self, label_widget=None): + content_widget = QtWidgets.QWidget(self) + content_widget.setObjectName("ContentWidget") + content_widget.setProperty("content_state", "") + + content_layout = QtWidgets.QGridLayout(content_widget) + content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0) + + body_widget = ExpandingWidget(self.schema_data["label"], self) + body_widget.set_content_widget(content_widget) + + label_widget = body_widget.label_widget + + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + if not body_widget: + main_layout.addWidget(content_widget) + else: + main_layout.addWidget(body_widget) + + self.label_widget = label_widget + self.body_widget = body_widget + self.content_layout = content_layout + + if self.collapsable: + if not self.collapsed: + body_widget.toggle_content() + else: + body_widget.hide_toolbox(hide_content=False) + + for child_data in self.schema_data.get("children", []): + self.add_children_gui(child_data) + + any_visible = False + for input_field in self.input_fields: + if not input_field.hidden_by_role: + any_visible = True + break + + if not any_visible: + self.hide() + + def add_children_gui(self, child_configuration): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + + row = self.content_layout.rowCount() + if not getattr(klass, "is_input_type", False): + item = klass(child_configuration, self) + self.content_layout.addWidget(item, row, 0, 1, 2) + return item + + label_widget = None + item = klass(child_configuration, self) + if not item.expand_in_grid: + label = child_configuration.get("label") + if label is not None: + label_widget = GridLabelWidget(label, self) + self.content_layout.addWidget(label_widget, row, 0, 1, 1) + + item.create_ui(label_widget=label_widget) + item.value_changed.connect(self._on_value_change) + + if label_widget: + if item.hidden_by_role: + label_widget.hide() + label_widget.input_field = item + self.content_layout.addWidget(item, row, 1, 1, 1) + else: + self.content_layout.addWidget(item, row, 0, 1, 2) + + self.input_fields.append(item) + return item + + def update_style(self, is_overriden=None): + child_has_studio_override = self.child_has_studio_override + child_modified = self.child_modified + child_invalid = self.child_invalid + child_state = self.style_state( + child_has_studio_override, + child_invalid, + self.child_overriden, + child_modified + ) + if child_state: + child_state = "child-{}".format(child_state) + + if child_state != self._child_state: + self.body_widget.side_line_widget.setProperty("state", child_state) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) + self._child_state = child_state + + state = self.style_state( + self.had_studio_override, + child_invalid, + self.is_overriden, + self.is_modified + ) + if self._state == state: + return + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + self._state = state + + class LabelWidget(QtWidgets.QWidget): is_input_type = False @@ -3766,11 +4034,17 @@ TypeToKlass.types["list-strict"] = ListStrictWidget TypeToKlass.types["enum"] = EnumeratorWidget TypeToKlass.types["dict-modifiable"] = ModifiableDict # DEPRECATED - remove when removed from schemas -TypeToKlass.types["dict-item"] = DictWidget -TypeToKlass.types["dict"] = DictWidget -TypeToKlass.types["dict-invisible"] = DictInvisible -TypeToKlass.types["path-widget"] = PathWidget -TypeToKlass.types["form"] = DictFormWidget - -TypeToKlass.types["label"] = LabelWidget TypeToKlass.types["splitter"] = SplitterWidget +TypeToKlass.types["dict-item"] = DictWidget +TypeToKlass.types["dict-invisible"] = DictWidget +# --------------------------------------------- +TypeToKlass.types["dict"] = DictWidget +TypeToKlass.types["path-widget"] = PathWidget + +# Wrappers +TypeToKlass.types["form"] = FormItemWidget +TypeToKlass.types["collapsible-wrap"] = CollapsibleWrapperItem + +# UI items +TypeToKlass.types["label"] = LabelWidget +TypeToKlass.types["separator"] = SplitterWidget diff --git a/pype/tools/settings/settings/widgets/lib.py b/pype/tools/settings/settings/widgets/lib.py index 1ec46f92b9..9a6331009b 100644 --- a/pype/tools/settings/settings/widgets/lib.py +++ b/pype/tools/settings/settings/widgets/lib.py @@ -2,7 +2,11 @@ import os import re import json import copy -from pype.settings.lib import M_OVERRIDEN_KEY, M_ENVIRONMENT_KEY +from pype.settings.lib import ( + M_OVERRIDEN_KEY, + M_ENVIRONMENT_KEY, + M_DYNAMIC_KEY_LABEL +) from queue import Queue @@ -35,6 +39,8 @@ def convert_gui_data_with_metadata(data, ignored_keys=None): if key == "environments": output[M_ENVIRONMENT_KEY] = value + elif key == "dynamic_key_label": + output[M_DYNAMIC_KEY_LABEL] = value else: raise KeyError("Unknown metadata key \"{}\"".format(key)) @@ -51,6 +57,11 @@ def convert_data_to_gui_data(data, first=True): if M_ENVIRONMENT_KEY in data: data.pop(M_ENVIRONMENT_KEY) + if M_DYNAMIC_KEY_LABEL in data: + if METADATA_KEY not in data: + data[METADATA_KEY] = {} + data[METADATA_KEY]["dynamic_key_label"] = data.pop(M_DYNAMIC_KEY_LABEL) + for key, value in data.items(): output[key] = convert_data_to_gui_data(value, False) @@ -213,45 +224,62 @@ def _fill_inner_schemas(schema_data, schema_collection, schema_templates): if schema_data["type"] == "schema": raise ValueError("First item in schema data can't be schema.") - children = schema_data.get("children") - if not children: - return schema_data - - new_children = [] - for child in children: - child_type = child["type"] - if child_type == "schema": - schema_name = child["name"] - if schema_name not in schema_collection: - if schema_name in schema_templates: - raise KeyError(( - "Schema template \"{}\" is used as `schema`" - ).format(schema_name)) - raise KeyError( - "Schema \"{}\" was not found".format(schema_name) - ) - - filled_child = _fill_inner_schemas( - schema_collection[schema_name], - schema_collection, - schema_templates - ) - - elif child_type == "schema_template": - for filled_child in _fill_schema_template( - child, schema_collection, schema_templates - ): - new_children.append(filled_child) + children_key = "children" + object_type_key = "object_type" + for item_key in (children_key, object_type_key): + children = schema_data.get(item_key) + if not children: continue - else: - filled_child = _fill_inner_schemas( - child, schema_collection, schema_templates - ) + if object_type_key == item_key: + if not isinstance(children, dict): + continue + children = [children] - new_children.append(filled_child) + new_children = [] + for child in children: + child_type = child["type"] + if child_type == "schema": + schema_name = child["name"] + if schema_name not in schema_collection: + if schema_name in schema_templates: + raise KeyError(( + "Schema template \"{}\" is used as `schema`" + ).format(schema_name)) + raise KeyError( + "Schema \"{}\" was not found".format(schema_name) + ) - schema_data["children"] = new_children + filled_child = _fill_inner_schemas( + schema_collection[schema_name], + schema_collection, + schema_templates + ) + + elif child_type == "schema_template": + for filled_child in _fill_schema_template( + child, schema_collection, schema_templates + ): + new_children.append(filled_child) + continue + + else: + filled_child = _fill_inner_schemas( + child, schema_collection, schema_templates + ) + + new_children.append(filled_child) + + if item_key == object_type_key: + if len(new_children) != 1: + raise KeyError(( + "Failed to fill object type with type: {} | name {}" + ).format( + child_type, str(child.get("name")) + )) + new_children = new_children[0] + + schema_data[item_key] = new_children return schema_data diff --git a/pype/tools/settings/settings/widgets/widgets.py b/pype/tools/settings/settings/widgets/widgets.py index b64b1aa8ac..092591c165 100644 --- a/pype/tools/settings/settings/widgets/widgets.py +++ b/pype/tools/settings/settings/widgets/widgets.py @@ -1,4 +1,23 @@ from Qt import QtWidgets, QtCore, QtGui +from avalon.vendor import qtawesome + + +class IconButton(QtWidgets.QPushButton): + def __init__(self, icon_name, color, hover_color, *args, **kwargs): + super(IconButton, self).__init__(*args, **kwargs) + + self.icon = qtawesome.icon(icon_name, color=color) + self.hover_icon = qtawesome.icon(icon_name, color=hover_color) + + self.setIcon(self.icon) + + def enterEvent(self, event): + self.setIcon(self.hover_icon) + super(IconButton, self).enterEvent(event) + + def leaveEvent(self, event): + self.setIcon(self.icon) + super(IconButton, self).leaveEvent(event) class NumberSpinBox(QtWidgets.QDoubleSpinBox): @@ -32,6 +51,11 @@ class ComboBox(QtWidgets.QComboBox): super(ComboBox, self).__init__(*args, **kwargs) self.currentIndexChanged.connect(self._on_change) + self.setFocusPolicy(QtCore.Qt.StrongFocus) + + def wheelEvent(self, event): + if self.hasFocus(): + return super(ComboBox, self).wheelEvent(event) def _on_change(self, *args, **kwargs): self.value_changed.emit() @@ -47,35 +71,6 @@ class ComboBox(QtWidgets.QComboBox): return self.itemData(self.currentIndex(), role=QtCore.Qt.UserRole) -class PathInput(QtWidgets.QLineEdit): - def clear_end_path(self): - value = self.text().strip() - if value.endswith("/"): - while value and value[-1] == "/": - value = value[:-1] - self.setText(value) - - def keyPressEvent(self, event): - # Always change backslash `\` for forwardslash `/` - if event.key() == QtCore.Qt.Key_Backslash: - event.accept() - new_event = QtGui.QKeyEvent( - event.type(), - QtCore.Qt.Key_Slash, - event.modifiers(), - "/", - event.isAutoRepeat(), - event.count() - ) - QtWidgets.QApplication.sendEvent(self, new_event) - return - super(PathInput, self).keyPressEvent(event) - - def focusOutEvent(self, event): - super(PathInput, self).focusOutEvent(event) - self.clear_end_path() - - class ClickableWidget(QtWidgets.QWidget): clicked = QtCore.Signal() @@ -108,11 +103,11 @@ class ExpandingWidget(QtWidgets.QWidget): label_widget.setObjectName("DictLabel") before_label_widget = QtWidgets.QWidget(side_line_widget) - before_label_layout = QtWidgets.QVBoxLayout(before_label_widget) + before_label_layout = QtWidgets.QHBoxLayout(before_label_widget) before_label_layout.setContentsMargins(0, 0, 0, 0) after_label_widget = QtWidgets.QWidget(side_line_widget) - after_label_layout = QtWidgets.QVBoxLayout(after_label_widget) + after_label_layout = QtWidgets.QHBoxLayout(after_label_widget) after_label_layout.setContentsMargins(0, 0, 0, 0) spacer_widget = QtWidgets.QWidget(side_line_widget) @@ -158,6 +153,12 @@ class ExpandingWidget(QtWidgets.QWidget): self.content_widget.setVisible(not hide_content) self.parent().updateGeometry() + def show_toolbox(self): + self.toolbox_hidden = False + self.toggle_content(self.button_toggle.isChecked()) + + self.parent().updateGeometry() + def set_content_widget(self, content_widget): content_widget.setVisible(False) self.main_layout.addWidget(content_widget) diff --git a/pype/tools/standalonepublish/widgets/widget_family.py b/pype/tools/standalonepublish/widgets/widget_family.py index 1c8f2238fc..5c0c8ccd38 100644 --- a/pype/tools/standalonepublish/widgets/widget_family.py +++ b/pype/tools/standalonepublish/widgets/widget_family.py @@ -1,10 +1,11 @@ +import os from collections import namedtuple from Qt import QtWidgets, QtCore from . import HelpRole, FamilyRole, ExistsRole, PluginRole, PluginKeyRole from . import FamilyDescriptionWidget -from pype.api import config +from pype.api import get_project_settings class FamilyWidget(QtWidgets.QWidget): @@ -309,9 +310,14 @@ class FamilyWidget(QtWidgets.QWidget): def refresh(self): has_families = False - presets = config.get_presets().get('standalone_publish', {}) + settings = get_project_settings(os.environ['AVALON_PROJECT']) + sp_settings = settings.get('standalonepublisher', {}) + print(sp_settings) + + for key, creator in sp_settings.get("create", {}).items(): + if key == "__dynamic_keys_labels__": + continue - for key, creator in presets.get('families', {}).items(): creator = namedtuple("Creator", creator.keys())(*creator.values()) label = creator.label or creator.family diff --git a/pype/tools/tray/pype_tray.py b/pype/tools/tray/pype_tray.py index ea22b4c9c6..75c62592e3 100644 --- a/pype/tools/tray/pype_tray.py +++ b/pype/tools/tray/pype_tray.py @@ -4,7 +4,8 @@ import sys import platform from avalon import style from Qt import QtCore, QtGui, QtWidgets, QtSvg -from pype.api import config, Logger, resources +from pype.api import Logger, resources +from pype.settings.lib import get_system_settings, load_json_file import pype.version try: import configparser @@ -32,18 +33,11 @@ class TrayManager: self.errors = [] CURRENT_DIR = os.path.dirname(__file__) - self.modules_imports = config.load_json( + self.modules_imports = load_json_file( os.path.join(CURRENT_DIR, "modules_imports.json") ) - presets = config.get_presets(first_run=True) - menu_items = presets["tray"]["menu_items"] - try: - self.modules_usage = menu_items["item_usage"] - except Exception: - self.modules_usage = {} - self.log.critical("Couldn't find modules usage data.") - - self.module_attributes = menu_items.get("attributes") or {} + module_settings = get_system_settings()["modules"] + self.module_settings = module_settings self.icon_run = QtGui.QIcon( resources.get_resource("icons", "circle_green.png") @@ -76,23 +70,19 @@ class TrayManager: import_path = item.get("import_path") title = item.get("title") - item_usage = self.modules_usage.get(title) - if item_usage is None: - item_usage = self.modules_usage.get(import_path, True) - - if not item_usage: + module_data = self.module_settings.get(title) + if not module_data: if not title: title = import_path - self.log.info("{} - Module ignored".format(title)) + self.log.warning("{} - Module data not found".format(title)) continue - _attributes = self.module_attributes.get(title) - if _attributes is None: - _attributes = self.module_attributes.get(import_path) - - if _attributes: - item["attributes"] = _attributes + enabled = module_data.pop("enabled", True) + if not enabled: + self.log.debug("{} - Module is disabled".format(title)) + continue + item["attributes"] = module_data items.append(item) if items: @@ -196,7 +186,7 @@ class TrayManager: ) klass = getattr(module, "CLASS_DEFINIION", None) if not klass and attributes: - self.log.error(( + self.log.debug(( "There are defined attributes for module \"{}\" but" "module does not have defined \"CLASS_DEFINIION\"." ).format(import_path)) diff --git a/setup/maya/userSetup.py b/setup/maya/userSetup.py index bbf66846da..6ee008c5fc 100644 --- a/setup/maya/userSetup.py +++ b/setup/maya/userSetup.py @@ -1,5 +1,5 @@ import os -from pype.api import config +from pype.api import get_project_settings import pype.hosts.maya.lib as mlib from maya import cmds @@ -7,14 +7,15 @@ from maya import cmds print("starting PYPE usersetup") # build a shelf -presets = config.get_presets() -shelf_preset = presets['maya'].get('project_shelf') +settings = get_project_settings(os.environ['AVALON_PROJECT']) +shelf_preset = settings['maya'].get('project_shelf') if shelf_preset: project = os.environ["AVALON_PROJECT"] - icon_path = os.path.join(os.environ['PYPE_PROJECT_SCRIPTS'], project,"icons") + icon_path = os.path.join(os.environ['PYPE_PROJECT_SCRIPTS'], + project, "icons") icon_path = os.path.abspath(icon_path) for i in shelf_preset['imports']: