OP-2019 - merge develop
16
.github/pull_request_template.md
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
## Brief description
|
||||
First sentence is brief description.
|
||||
|
||||
## Description
|
||||
Next paragraf is more elaborate text with more info. This will be displayed for example in collapsed form under the first sentence in a changelog.
|
||||
|
||||
## Additional info
|
||||
The rest will be ignored in changelog and should contain any additional
|
||||
technical information.
|
||||
|
||||
## Documentation (add _"type: documentation"_ label)
|
||||
[feature_documentation](future_url_after_it_will_be_merged)
|
||||
|
||||
## Testing notes:
|
||||
1. start with this step
|
||||
2. follow this step
|
||||
2
.github/workflows/prerelease.yml
vendored
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
id: version
|
||||
if: steps.version_type.outputs.type != 'skip'
|
||||
run: |
|
||||
RESULT=$(python ./tools/ci_tools.py --nightly)
|
||||
RESULT=$(python ./tools/ci_tools.py --nightly --github_token ${{ secrets.GITHUB_TOKEN }})
|
||||
|
||||
echo ::set-output name=next_tag::$RESULT
|
||||
|
||||
|
|
|
|||
203
CHANGELOG.md
|
|
@ -1,111 +1,138 @@
|
|||
# Changelog
|
||||
|
||||
## [3.6.0-nightly.5](https://github.com/pypeclub/OpenPype/tree/HEAD)
|
||||
## [3.7.0-nightly.7](https://github.com/pypeclub/OpenPype/tree/HEAD)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.5.0...HEAD)
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.6.4...HEAD)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- docs\[website\]: Add Ellipse Studio \(logo\) as an OpenPype contributor [\#2324](https://github.com/pypeclub/OpenPype/pull/2324)
|
||||
|
||||
**🆕 New features**
|
||||
|
||||
- Maya : Colorspace configuration [\#2170](https://github.com/pypeclub/OpenPype/pull/2170)
|
||||
- Blender: Added support for audio [\#2168](https://github.com/pypeclub/OpenPype/pull/2168)
|
||||
- Flame: a host basic integration [\#2165](https://github.com/pypeclub/OpenPype/pull/2165)
|
||||
- Houdini: simple HDA workflow [\#2072](https://github.com/pypeclub/OpenPype/pull/2072)
|
||||
- Settings UI use OpenPype styles [\#2296](https://github.com/pypeclub/OpenPype/pull/2296)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
- Add both side availability on Site Sync sites to Loader [\#2220](https://github.com/pypeclub/OpenPype/pull/2220)
|
||||
- Tools: Center loader and library loader on show [\#2219](https://github.com/pypeclub/OpenPype/pull/2219)
|
||||
- Maya : Validate shape zero [\#2212](https://github.com/pypeclub/OpenPype/pull/2212)
|
||||
- Maya : validate unique names [\#2211](https://github.com/pypeclub/OpenPype/pull/2211)
|
||||
- Tools: OpenPype stylesheet in workfiles tool [\#2208](https://github.com/pypeclub/OpenPype/pull/2208)
|
||||
- Ftrack: Replace Queue with deque in event handlers logic [\#2204](https://github.com/pypeclub/OpenPype/pull/2204)
|
||||
- Tools: New select context dialog [\#2200](https://github.com/pypeclub/OpenPype/pull/2200)
|
||||
- Maya : Validate mesh ngons [\#2199](https://github.com/pypeclub/OpenPype/pull/2199)
|
||||
- Delivery: Check 'frame' key in template for sequence delivery [\#2196](https://github.com/pypeclub/OpenPype/pull/2196)
|
||||
- Usage of tools code [\#2185](https://github.com/pypeclub/OpenPype/pull/2185)
|
||||
- Settings: Dictionary based on project roots [\#2184](https://github.com/pypeclub/OpenPype/pull/2184)
|
||||
- Subset name: Be able to pass asset document to get subset name [\#2179](https://github.com/pypeclub/OpenPype/pull/2179)
|
||||
- Tools: Experimental tools [\#2167](https://github.com/pypeclub/OpenPype/pull/2167)
|
||||
- Loader: Refactor and use OpenPype stylesheets [\#2166](https://github.com/pypeclub/OpenPype/pull/2166)
|
||||
- Add loader for linked smart objects in photoshop [\#2149](https://github.com/pypeclub/OpenPype/pull/2149)
|
||||
- Settings: Webpublisher in hosts enum [\#2367](https://github.com/pypeclub/OpenPype/pull/2367)
|
||||
- Hiero: python3 compatibility [\#2365](https://github.com/pypeclub/OpenPype/pull/2365)
|
||||
- Burnins: Be able recognize mxf OPAtom format [\#2361](https://github.com/pypeclub/OpenPype/pull/2361)
|
||||
- Local settings: Copyable studio paths [\#2349](https://github.com/pypeclub/OpenPype/pull/2349)
|
||||
- Assets Widget: Clear model on project change [\#2345](https://github.com/pypeclub/OpenPype/pull/2345)
|
||||
- General: OpenPype default modules hierarchy [\#2338](https://github.com/pypeclub/OpenPype/pull/2338)
|
||||
- General: FFprobe error exception contain original error message [\#2328](https://github.com/pypeclub/OpenPype/pull/2328)
|
||||
- Resolve: Add experimental button to menu [\#2325](https://github.com/pypeclub/OpenPype/pull/2325)
|
||||
- Hiero: Add experimental tools action [\#2323](https://github.com/pypeclub/OpenPype/pull/2323)
|
||||
- Input links: Cleanup and unification of differences [\#2322](https://github.com/pypeclub/OpenPype/pull/2322)
|
||||
- General: Don't validate vendor bin with executing them [\#2317](https://github.com/pypeclub/OpenPype/pull/2317)
|
||||
- General: Multilayer EXRs support [\#2315](https://github.com/pypeclub/OpenPype/pull/2315)
|
||||
- General: Run process log stderr as info log level [\#2309](https://github.com/pypeclub/OpenPype/pull/2309)
|
||||
- General: Reduce vendor imports [\#2305](https://github.com/pypeclub/OpenPype/pull/2305)
|
||||
- Tools: Cleanup of unused classes [\#2304](https://github.com/pypeclub/OpenPype/pull/2304)
|
||||
- Project Manager: Added ability to delete project [\#2298](https://github.com/pypeclub/OpenPype/pull/2298)
|
||||
- Ftrack: Synchronize input links [\#2287](https://github.com/pypeclub/OpenPype/pull/2287)
|
||||
- Nuke: extract baked review videos presets [\#2248](https://github.com/pypeclub/OpenPype/pull/2248)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Maya : multiple subsets review broken [\#2210](https://github.com/pypeclub/OpenPype/pull/2210)
|
||||
- Fix - different command used for Linux and Mac OS [\#2207](https://github.com/pypeclub/OpenPype/pull/2207)
|
||||
- Tools: Workfiles tool don't use avalon widgets [\#2205](https://github.com/pypeclub/OpenPype/pull/2205)
|
||||
- Ftrack: Fill missing ftrack id on mongo project [\#2203](https://github.com/pypeclub/OpenPype/pull/2203)
|
||||
- Project Manager: Fix copying of tasks [\#2191](https://github.com/pypeclub/OpenPype/pull/2191)
|
||||
- StandalonePublisher: Source validator don't expect representations [\#2190](https://github.com/pypeclub/OpenPype/pull/2190)
|
||||
- Blender: Fix trying to pack an image when the shader node has no texture [\#2183](https://github.com/pypeclub/OpenPype/pull/2183)
|
||||
- MacOS: Launching of applications may cause Permissions error [\#2175](https://github.com/pypeclub/OpenPype/pull/2175)
|
||||
- Maya: Aspect ratio [\#2174](https://github.com/pypeclub/OpenPype/pull/2174)
|
||||
- Blender: Fix 'Deselect All' with object not in 'Object Mode' [\#2163](https://github.com/pypeclub/OpenPype/pull/2163)
|
||||
- Maya: Fix hotbox broken by scriptsmenu [\#2151](https://github.com/pypeclub/OpenPype/pull/2151)
|
||||
- Added validator for source files for Standalone Publisher [\#2138](https://github.com/pypeclub/OpenPype/pull/2138)
|
||||
- Flame: fix ftrack publisher [\#2381](https://github.com/pypeclub/OpenPype/pull/2381)
|
||||
- hiero: solve custom ocio path [\#2379](https://github.com/pypeclub/OpenPype/pull/2379)
|
||||
- hiero: fix workio and flatten [\#2378](https://github.com/pypeclub/OpenPype/pull/2378)
|
||||
- Nuke: fixing menu re-drawing during context change [\#2374](https://github.com/pypeclub/OpenPype/pull/2374)
|
||||
- Webpublisher: Fix assignment of families of TVpaint instances [\#2373](https://github.com/pypeclub/OpenPype/pull/2373)
|
||||
- Nuke: fixing node name based on switched asset name [\#2369](https://github.com/pypeclub/OpenPype/pull/2369)
|
||||
- JobQueue: Fix loading of settings [\#2362](https://github.com/pypeclub/OpenPype/pull/2362)
|
||||
- Tools: Placeholder color [\#2359](https://github.com/pypeclub/OpenPype/pull/2359)
|
||||
- Launcher: Minimize button on MacOs [\#2355](https://github.com/pypeclub/OpenPype/pull/2355)
|
||||
- StandalonePublisher: Fix import of constant [\#2354](https://github.com/pypeclub/OpenPype/pull/2354)
|
||||
- Adobe products show issue [\#2347](https://github.com/pypeclub/OpenPype/pull/2347)
|
||||
- Maya Look Assigner: Fix Python 3 compatibility [\#2343](https://github.com/pypeclub/OpenPype/pull/2343)
|
||||
- Remove wrongly used host for hook [\#2342](https://github.com/pypeclub/OpenPype/pull/2342)
|
||||
- Tools: Use Qt context on tools show [\#2340](https://github.com/pypeclub/OpenPype/pull/2340)
|
||||
- Flame: Fix default argument value in custom dictionary [\#2339](https://github.com/pypeclub/OpenPype/pull/2339)
|
||||
- Timers Manager: Disable auto stop timer on linux platform [\#2334](https://github.com/pypeclub/OpenPype/pull/2334)
|
||||
- nuke: bake preset single input exception [\#2331](https://github.com/pypeclub/OpenPype/pull/2331)
|
||||
- Hiero: fixing multiple templates at a hierarchy parent [\#2330](https://github.com/pypeclub/OpenPype/pull/2330)
|
||||
- Fix - provider icons are pulled from a folder [\#2326](https://github.com/pypeclub/OpenPype/pull/2326)
|
||||
- InputLinks: Typo in "inputLinks" key [\#2314](https://github.com/pypeclub/OpenPype/pull/2314)
|
||||
- Deadline timeout and logging [\#2312](https://github.com/pypeclub/OpenPype/pull/2312)
|
||||
- nuke: do not multiply representation on class method [\#2311](https://github.com/pypeclub/OpenPype/pull/2311)
|
||||
- Workfiles tool: Fix task formatting [\#2306](https://github.com/pypeclub/OpenPype/pull/2306)
|
||||
- Delivery: Fix delivery paths created on windows [\#2302](https://github.com/pypeclub/OpenPype/pull/2302)
|
||||
- Maya: Deadline - fix limit groups [\#2295](https://github.com/pypeclub/OpenPype/pull/2295)
|
||||
- Royal Render: Fix plugin order and OpenPype auto-detection [\#2291](https://github.com/pypeclub/OpenPype/pull/2291)
|
||||
- Alternate site for site sync doesnt work for sequences [\#2284](https://github.com/pypeclub/OpenPype/pull/2284)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Settings: Site sync project settings improvement [\#2193](https://github.com/pypeclub/OpenPype/pull/2193)
|
||||
- Add validate active site button to sync queue on a project [\#2176](https://github.com/pypeclub/OpenPype/pull/2176)
|
||||
- Bump pillow from 8.2.0 to 8.3.2 [\#2162](https://github.com/pypeclub/OpenPype/pull/2162)
|
||||
- Linux : flip updating submodules logic [\#2357](https://github.com/pypeclub/OpenPype/pull/2357)
|
||||
- Update of avalon-core [\#2346](https://github.com/pypeclub/OpenPype/pull/2346)
|
||||
- Maya: configurable model top level validation [\#2321](https://github.com/pypeclub/OpenPype/pull/2321)
|
||||
|
||||
## [3.6.4](https://github.com/pypeclub/OpenPype/tree/3.6.4) (2021-11-23)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.7.0-nightly.1...3.6.4)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Nuke: inventory update removes all loaded read nodes [\#2294](https://github.com/pypeclub/OpenPype/pull/2294)
|
||||
|
||||
## [3.6.3](https://github.com/pypeclub/OpenPype/tree/3.6.3) (2021-11-19)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.6.3-nightly.1...3.6.3)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Deadline: Fix publish targets [\#2280](https://github.com/pypeclub/OpenPype/pull/2280)
|
||||
|
||||
## [3.6.2](https://github.com/pypeclub/OpenPype/tree/3.6.2) (2021-11-18)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.6.2-nightly.2...3.6.2)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
- Tools: Assets widget [\#2265](https://github.com/pypeclub/OpenPype/pull/2265)
|
||||
- SceneInventory: Choose loader in asset switcher [\#2262](https://github.com/pypeclub/OpenPype/pull/2262)
|
||||
- Style: New fonts in OpenPype style [\#2256](https://github.com/pypeclub/OpenPype/pull/2256)
|
||||
- Tools: Tasks widget [\#2251](https://github.com/pypeclub/OpenPype/pull/2251)
|
||||
- Tools: Creator in OpenPype [\#2244](https://github.com/pypeclub/OpenPype/pull/2244)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Tools: Parenting of tools in Nuke and Hiero [\#2266](https://github.com/pypeclub/OpenPype/pull/2266)
|
||||
- limiting validator to specific editorial hosts [\#2264](https://github.com/pypeclub/OpenPype/pull/2264)
|
||||
- Tools: Select Context dialog attribute fix [\#2261](https://github.com/pypeclub/OpenPype/pull/2261)
|
||||
- Maya: Render publishing fails on linux [\#2260](https://github.com/pypeclub/OpenPype/pull/2260)
|
||||
- LookAssigner: Fix tool reopen [\#2259](https://github.com/pypeclub/OpenPype/pull/2259)
|
||||
- Standalone: editorial not publishing thumbnails on all subsets [\#2258](https://github.com/pypeclub/OpenPype/pull/2258)
|
||||
- Burnins: Support mxf metadata [\#2247](https://github.com/pypeclub/OpenPype/pull/2247)
|
||||
|
||||
## [3.6.1](https://github.com/pypeclub/OpenPype/tree/3.6.1) (2021-11-16)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.6.1-nightly.1...3.6.1)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Loader doesn't allow changing of version before loading [\#2254](https://github.com/pypeclub/OpenPype/pull/2254)
|
||||
|
||||
## [3.6.0](https://github.com/pypeclub/OpenPype/tree/3.6.0) (2021-11-15)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.6.0-nightly.6...3.6.0)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
- Tools: SceneInventory in OpenPype [\#2255](https://github.com/pypeclub/OpenPype/pull/2255)
|
||||
- Tools: Subset manager in OpenPype [\#2243](https://github.com/pypeclub/OpenPype/pull/2243)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Ftrack: Sync project ftrack id cache issue [\#2250](https://github.com/pypeclub/OpenPype/pull/2250)
|
||||
- Ftrack: Session creation and Prepare project [\#2245](https://github.com/pypeclub/OpenPype/pull/2245)
|
||||
|
||||
## [3.5.0](https://github.com/pypeclub/OpenPype/tree/3.5.0) (2021-10-17)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.5.0-nightly.8...3.5.0)
|
||||
|
||||
**Deprecated:**
|
||||
|
||||
- Maya: Change mayaAscii family to mayaScene [\#2106](https://github.com/pypeclub/OpenPype/pull/2106)
|
||||
|
||||
**🆕 New features**
|
||||
|
||||
- Added project and task into context change message in Maya [\#2131](https://github.com/pypeclub/OpenPype/pull/2131)
|
||||
- Add ExtractBurnin to photoshop review [\#2124](https://github.com/pypeclub/OpenPype/pull/2124)
|
||||
- PYPE-1218 - changed namespace to contain subset name in Maya [\#2114](https://github.com/pypeclub/OpenPype/pull/2114)
|
||||
- Added running configurable disk mapping command before start of OP [\#2091](https://github.com/pypeclub/OpenPype/pull/2091)
|
||||
- SFTP provider [\#2073](https://github.com/pypeclub/OpenPype/pull/2073)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
- Maya: make rig validators configurable in settings [\#2137](https://github.com/pypeclub/OpenPype/pull/2137)
|
||||
- Settings: Updated readme for entity types in settings [\#2132](https://github.com/pypeclub/OpenPype/pull/2132)
|
||||
- Nuke: unified clip loader [\#2128](https://github.com/pypeclub/OpenPype/pull/2128)
|
||||
- Settings UI: Project model refreshing and sorting [\#2104](https://github.com/pypeclub/OpenPype/pull/2104)
|
||||
- Create Read From Rendered - Disable Relative paths by default [\#2093](https://github.com/pypeclub/OpenPype/pull/2093)
|
||||
- Added choosing different dirmap mapping if workfile synched locally [\#2088](https://github.com/pypeclub/OpenPype/pull/2088)
|
||||
- General: Remove IdleManager module [\#2084](https://github.com/pypeclub/OpenPype/pull/2084)
|
||||
- Tray UI: Message box about missing settings defaults [\#2080](https://github.com/pypeclub/OpenPype/pull/2080)
|
||||
- Tray UI: Show menu where first click happened [\#2079](https://github.com/pypeclub/OpenPype/pull/2079)
|
||||
- Global: add global validators to settings [\#2078](https://github.com/pypeclub/OpenPype/pull/2078)
|
||||
- Use CRF for burnin when available [\#2070](https://github.com/pypeclub/OpenPype/pull/2070)
|
||||
- Project manager: Filter first item after selection of project [\#2069](https://github.com/pypeclub/OpenPype/pull/2069)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Maya: fix model publishing [\#2130](https://github.com/pypeclub/OpenPype/pull/2130)
|
||||
- Fix - oiiotool wasn't recognized even if present [\#2129](https://github.com/pypeclub/OpenPype/pull/2129)
|
||||
- General: Disk mapping group [\#2120](https://github.com/pypeclub/OpenPype/pull/2120)
|
||||
- Hiero: publishing effect first time makes wrong resources path [\#2115](https://github.com/pypeclub/OpenPype/pull/2115)
|
||||
- Add startup script for Houdini Core. [\#2110](https://github.com/pypeclub/OpenPype/pull/2110)
|
||||
- TVPaint: Behavior name of loop also accept repeat [\#2109](https://github.com/pypeclub/OpenPype/pull/2109)
|
||||
- Ftrack: Project settings save custom attributes skip unknown attributes [\#2103](https://github.com/pypeclub/OpenPype/pull/2103)
|
||||
- Blender: Fix NoneType error when animation\_data is missing for a rig [\#2101](https://github.com/pypeclub/OpenPype/pull/2101)
|
||||
- Fix broken import in sftp provider [\#2100](https://github.com/pypeclub/OpenPype/pull/2100)
|
||||
- Global: Fix docstring on publish plugin extract review [\#2097](https://github.com/pypeclub/OpenPype/pull/2097)
|
||||
- Delivery Action Files Sequence fix [\#2096](https://github.com/pypeclub/OpenPype/pull/2096)
|
||||
- General: Cloud mongo ca certificate issue [\#2095](https://github.com/pypeclub/OpenPype/pull/2095)
|
||||
- TVPaint: Creator use context from workfile [\#2087](https://github.com/pypeclub/OpenPype/pull/2087)
|
||||
- Blender: fix texture missing when publishing blend files [\#2085](https://github.com/pypeclub/OpenPype/pull/2085)
|
||||
- General: Startup validations oiio tool path fix on linux [\#2083](https://github.com/pypeclub/OpenPype/pull/2083)
|
||||
- Deadline: Collect deadline server does not check existence of deadline key [\#2082](https://github.com/pypeclub/OpenPype/pull/2082)
|
||||
- Blender: fixed Curves with modifiers in Rigs [\#2081](https://github.com/pypeclub/OpenPype/pull/2081)
|
||||
- Nuke UI scaling [\#2077](https://github.com/pypeclub/OpenPype/pull/2077)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Bump pywin32 from 300 to 301 [\#2086](https://github.com/pypeclub/OpenPype/pull/2086)
|
||||
|
||||
## [3.4.1](https://github.com/pypeclub/OpenPype/tree/3.4.1) (2021-09-23)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.4.1-nightly.1...3.4.1)
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.n
|
|||
ncurses \
|
||||
ncurses-devel \
|
||||
qt5-qtbase-devel \
|
||||
xcb-util-wm \
|
||||
xcb-util-renderutil \
|
||||
&& yum clean all
|
||||
|
||||
# we need to build our own patchelf
|
||||
|
|
@ -92,7 +94,8 @@ RUN source $HOME/.bashrc \
|
|||
RUN cp /usr/lib64/libffi* ./build/exe.linux-x86_64-3.7/lib \
|
||||
&& cp /usr/lib64/libssl* ./build/exe.linux-x86_64-3.7/lib \
|
||||
&& cp /usr/lib64/libcrypto* ./build/exe.linux-x86_64-3.7/lib \
|
||||
&& cp /root/.pyenv/versions/${OPENPYPE_PYTHON_VERSION}/lib/libpython* ./build/exe.linux-x86_64-3.7/lib
|
||||
&& cp /root/.pyenv/versions/${OPENPYPE_PYTHON_VERSION}/lib/libpython* ./build/exe.linux-x86_64-3.7/lib \
|
||||
&& cp /usr/lib64/libxcb* ./build/exe.linux-x86_64-3.7/vendor/python/PySide2/Qt/lib
|
||||
|
||||
RUN cd /opt/openpype \
|
||||
rm -rf ./vendor/bin
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import tempfile
|
|||
from pathlib import Path
|
||||
from typing import Union, Callable, List, Tuple
|
||||
import hashlib
|
||||
import platform
|
||||
|
||||
from zipfile import ZipFile, BadZipFile
|
||||
|
||||
|
|
@ -196,21 +197,23 @@ class OpenPypeVersion(semver.VersionInfo):
|
|||
return str(self.finalize_version())
|
||||
|
||||
@staticmethod
|
||||
def version_in_str(string: str) -> Tuple:
|
||||
def version_in_str(string: str) -> Union[None, OpenPypeVersion]:
|
||||
"""Find OpenPype version in given string.
|
||||
|
||||
Args:
|
||||
string (str): string to search.
|
||||
|
||||
Returns:
|
||||
tuple: True/False and OpenPypeVersion if found.
|
||||
OpenPypeVersion: of detected or None.
|
||||
|
||||
"""
|
||||
m = re.search(OpenPypeVersion._VERSION_REGEX, string)
|
||||
if not m:
|
||||
return False, None
|
||||
return None
|
||||
version = OpenPypeVersion.parse(string[m.start():m.end()])
|
||||
return True, version
|
||||
if "staging" in string[m.start():m.end()]:
|
||||
version.staging = True
|
||||
return version
|
||||
|
||||
@classmethod
|
||||
def parse(cls, version):
|
||||
|
|
@ -531,6 +534,7 @@ class BootstrapRepos:
|
|||
processed_path = file
|
||||
self._print(f"- processing {processed_path}")
|
||||
|
||||
|
||||
checksums.append(
|
||||
(
|
||||
sha256sum(file.as_posix()),
|
||||
|
|
@ -542,7 +546,10 @@ class BootstrapRepos:
|
|||
|
||||
checksums_str = ""
|
||||
for c in checksums:
|
||||
checksums_str += "{}:{}\n".format(c[0], c[1])
|
||||
file_str = c[1]
|
||||
if platform.system().lower() == "windows":
|
||||
file_str = c[1].as_posix().replace("\\", "/")
|
||||
checksums_str += "{}:{}\n".format(c[0], file_str)
|
||||
zip_file.writestr("checksums", checksums_str)
|
||||
# test if zip is ok
|
||||
zip_file.testzip()
|
||||
|
|
@ -563,6 +570,8 @@ class BootstrapRepos:
|
|||
and string with reason as second.
|
||||
|
||||
"""
|
||||
if os.getenv("OPENPYPE_DONT_VALIDATE_VERSION"):
|
||||
return True, "Disabled validation"
|
||||
if not path.exists():
|
||||
return False, "Path doesn't exist"
|
||||
|
||||
|
|
@ -589,13 +598,16 @@ class BootstrapRepos:
|
|||
|
||||
# calculate and compare checksums in the zip file
|
||||
for file in checksums:
|
||||
file_name = file[1]
|
||||
if platform.system().lower() == "windows":
|
||||
file_name = file_name.replace("/", "\\")
|
||||
h = hashlib.sha256()
|
||||
try:
|
||||
h.update(zip_file.read(file[1]))
|
||||
h.update(zip_file.read(file_name))
|
||||
except FileNotFoundError:
|
||||
return False, f"Missing file [ {file[1]} ]"
|
||||
return False, f"Missing file [ {file_name} ]"
|
||||
if h.hexdigest() != file[0]:
|
||||
return False, f"Invalid checksum on {file[1]}"
|
||||
return False, f"Invalid checksum on {file_name}"
|
||||
|
||||
# get list of files in zip minus `checksums` file itself
|
||||
# and turn in to set to compare against list of files
|
||||
|
|
@ -604,7 +616,7 @@ class BootstrapRepos:
|
|||
files_in_zip = zip_file.namelist()
|
||||
files_in_zip.remove("checksums")
|
||||
files_in_zip = set(files_in_zip)
|
||||
files_in_checksum = set([file[1] for file in checksums])
|
||||
files_in_checksum = {file[1] for file in checksums}
|
||||
diff = files_in_zip.difference(files_in_checksum)
|
||||
if diff:
|
||||
return False, f"Missing files {diff}"
|
||||
|
|
@ -628,16 +640,19 @@ class BootstrapRepos:
|
|||
]
|
||||
files_in_dir.remove("checksums")
|
||||
files_in_dir = set(files_in_dir)
|
||||
files_in_checksum = set([file[1] for file in checksums])
|
||||
files_in_checksum = {file[1] for file in checksums}
|
||||
|
||||
for file in checksums:
|
||||
file_name = file[1]
|
||||
if platform.system().lower() == "windows":
|
||||
file_name = file_name.replace("/", "\\")
|
||||
try:
|
||||
current = sha256sum((path / file[1]).as_posix())
|
||||
current = sha256sum((path / file_name).as_posix())
|
||||
except FileNotFoundError:
|
||||
return False, f"Missing file [ {file[1]} ]"
|
||||
return False, f"Missing file [ {file_name} ]"
|
||||
|
||||
if file[0] != current:
|
||||
return False, f"Invalid checksum on {file[1]}"
|
||||
return False, f"Invalid checksum on {file_name}"
|
||||
diff = files_in_dir.difference(files_in_checksum)
|
||||
if diff:
|
||||
return False, f"Missing files {diff}"
|
||||
|
|
@ -1161,9 +1176,9 @@ class BootstrapRepos:
|
|||
name = item.name if item.is_dir() else item.stem
|
||||
result = OpenPypeVersion.version_in_str(name)
|
||||
|
||||
if result[0]:
|
||||
if result:
|
||||
detected_version: OpenPypeVersion
|
||||
detected_version = result[1]
|
||||
detected_version = result
|
||||
|
||||
if item.is_dir() and not self._is_openpype_in_dir(
|
||||
item, detected_version
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ def validate_mongo_connection(cnx: str) -> (bool, str):
|
|||
return False, "Not mongodb schema"
|
||||
|
||||
kwargs = {
|
||||
"serverSelectionTimeoutMS": 2000
|
||||
"serverSelectionTimeoutMS": os.environ.get("AVALON_TIMEOUT", 2000)
|
||||
}
|
||||
# Add certificate path if should be required
|
||||
if should_add_certificate_path_to_mongo_url(cnx):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Definition of Igniter version."""
|
||||
|
||||
__version__ = "1.0.1"
|
||||
__version__ = "1.0.2"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from .lib import (
|
|||
version_up,
|
||||
get_asset,
|
||||
get_hierarchy,
|
||||
get_workdir_data,
|
||||
get_version_from_path,
|
||||
get_last_version_from_path,
|
||||
get_app_environments_for_context,
|
||||
|
|
|
|||
|
|
@ -158,7 +158,9 @@ def extractenvironments(output_json_path, project, asset, task, app):
|
|||
@click.option("-d", "--debug", is_flag=True, help="Print debug messages")
|
||||
@click.option("-t", "--targets", help="Targets module", default=None,
|
||||
multiple=True)
|
||||
def publish(debug, paths, targets):
|
||||
@click.option("-g", "--gui", is_flag=True,
|
||||
help="Show Publish UI", default=False)
|
||||
def publish(debug, paths, targets, gui):
|
||||
"""Start CLI publishing.
|
||||
|
||||
Publish collects json from paths provided as an argument.
|
||||
|
|
@ -166,7 +168,7 @@ def publish(debug, paths, targets):
|
|||
"""
|
||||
if debug:
|
||||
os.environ['OPENPYPE_DEBUG'] = '3'
|
||||
PypeCommands.publish(list(paths), targets)
|
||||
PypeCommands.publish(list(paths), targets, gui)
|
||||
|
||||
|
||||
@main.command()
|
||||
|
|
@ -354,6 +356,56 @@ def run(script):
|
|||
"--pyargs",
|
||||
help="Run tests from package",
|
||||
default=None)
|
||||
def runtests(folder, mark, pyargs):
|
||||
@click.option("-t",
|
||||
"--test_data_folder",
|
||||
help="Unzipped directory path of test file",
|
||||
default=None)
|
||||
@click.option("-s",
|
||||
"--persist",
|
||||
help="Persist test DB and published files after test end",
|
||||
default=None)
|
||||
@click.option("-a",
|
||||
"--app_variant",
|
||||
help="Provide specific app variant for test, empty for latest",
|
||||
default=None)
|
||||
def runtests(folder, mark, pyargs, test_data_folder, persist, app_variant):
|
||||
"""Run all automatic tests after proper initialization via start.py"""
|
||||
PypeCommands().run_tests(folder, mark, pyargs)
|
||||
PypeCommands().run_tests(folder, mark, pyargs, test_data_folder,
|
||||
persist, app_variant)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option("-d", "--debug",
|
||||
is_flag=True, help=("Run process in debug mode"))
|
||||
@click.option("-a", "--active_site", required=True,
|
||||
help="Name of active stie")
|
||||
def syncserver(debug, active_site):
|
||||
"""Run sync site server in background.
|
||||
|
||||
Some Site Sync use cases need to expose site to another one.
|
||||
For example if majority of artists work in studio, they are not using
|
||||
SS at all, but if you want to expose published assets to 'studio' site
|
||||
to SFTP for only a couple of artists, some background process must
|
||||
mark published assets to live on multiple sites (they might be
|
||||
physically in same location - mounted shared disk).
|
||||
|
||||
Process mimics OP Tray with specific 'active_site' name, all
|
||||
configuration for this "dummy" user comes from Setting or Local
|
||||
Settings (configured by starting OP Tray with env
|
||||
var OPENPYPE_LOCAL_ID set to 'active_site'.
|
||||
"""
|
||||
if debug:
|
||||
os.environ['OPENPYPE_DEBUG'] = '3'
|
||||
PypeCommands().syncserver(active_site)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument("directory")
|
||||
def repack_version(directory):
|
||||
"""Repack OpenPype version from directory.
|
||||
|
||||
This command will re-create zip file from specified directory,
|
||||
recalculating file checksums. It will try to use version detected in
|
||||
directory name.
|
||||
"""
|
||||
PypeCommands().repack_version(directory)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class LaunchFoundryAppsWindows(PreLaunchHook):
|
|||
|
||||
# Should be as last hook because must change launch arguments to string
|
||||
order = 1000
|
||||
app_groups = ["nuke", "nukex", "hiero", "nukestudio"]
|
||||
app_groups = ["nuke", "nukex", "hiero", "nukestudio", "aftereffects"]
|
||||
platforms = ["windows"]
|
||||
|
||||
def execute(self):
|
||||
|
|
|
|||
|
|
@ -49,7 +49,3 @@ class NonPythonHostHook(PreLaunchHook):
|
|||
if remainders:
|
||||
self.launch_context.launch_args.extend(remainders)
|
||||
|
||||
# This must be set otherwise it wouldn't be possible to catch output
|
||||
# when build OpenPype is used.
|
||||
self.launch_context.kwargs["stdout"] = subprocess.DEVNULL
|
||||
self.launch_context.kwargs["stderr"] = subprocess.DEVNULL
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import logging
|
|||
|
||||
from avalon import io
|
||||
from avalon import api as avalon
|
||||
from avalon.vendor import Qt
|
||||
from Qt import QtWidgets
|
||||
from openpype import lib, api
|
||||
import pyblish.api as pyblish
|
||||
import openpype.hosts.aftereffects
|
||||
|
|
@ -41,10 +41,10 @@ def check_inventory():
|
|||
|
||||
# Warn about outdated containers.
|
||||
print("Starting new QApplication..")
|
||||
app = Qt.QtWidgets.QApplication(sys.argv)
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
message_box = Qt.QtWidgets.QMessageBox()
|
||||
message_box.setIcon(Qt.QtWidgets.QMessageBox.Warning)
|
||||
message_box = QtWidgets.QMessageBox()
|
||||
message_box.setIcon(QtWidgets.QMessageBox.Warning)
|
||||
msg = "There are outdated containers in the scene."
|
||||
message_box.setText(msg)
|
||||
message_box.exec_()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import openpype.api
|
||||
from avalon.vendor import Qt
|
||||
from Qt import QtWidgets
|
||||
from avalon import aftereffects
|
||||
|
||||
import logging
|
||||
|
|
@ -56,7 +56,7 @@ class CreateRender(openpype.api.Creator):
|
|||
stub.rename_item(item.id, stub.PUBLISH_ICON + self.data["subset"])
|
||||
|
||||
def _show_msg(self, txt):
|
||||
msg = Qt.QtWidgets.QMessageBox()
|
||||
msg.setIcon(Qt.QtWidgets.QMessageBox.Warning)
|
||||
msg = QtWidgets.QMessageBox()
|
||||
msg.setIcon(QtWidgets.QMessageBox.Warning)
|
||||
msg.setText(txt)
|
||||
msg.exec_()
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Close AE after publish. For Webpublishing only."""
|
||||
import os
|
||||
|
||||
import pyblish.api
|
||||
|
||||
from avalon import aftereffects
|
||||
|
||||
|
||||
class CloseAE(pyblish.api.ContextPlugin):
|
||||
"""Close AE after publish. For Webpublishing only.
|
||||
"""
|
||||
|
||||
order = pyblish.api.IntegratorOrder + 14
|
||||
label = "Close AE"
|
||||
optional = True
|
||||
active = True
|
||||
|
||||
hosts = ["aftereffects"]
|
||||
targets = ["remotepublish"]
|
||||
|
||||
def process(self, context):
|
||||
self.log.info("CloseAE")
|
||||
|
||||
stub = aftereffects.stub()
|
||||
self.log.info("Shutting down AE")
|
||||
stub.save()
|
||||
stub.close()
|
||||
self.log.info("AE closed")
|
||||
|
|
@ -19,9 +19,10 @@ class ExtractLocalRender(openpype.api.Extractor):
|
|||
staging_dir = instance.data["stagingDir"]
|
||||
self.log.info("staging_dir::{}".format(staging_dir))
|
||||
|
||||
stub.render(staging_dir)
|
||||
|
||||
# pull file name from Render Queue Output module
|
||||
render_q = stub.get_render_info()
|
||||
stub.render(staging_dir)
|
||||
if not render_q:
|
||||
raise ValueError("No file extension set in Render Queue")
|
||||
_, ext = os.path.splitext(os.path.basename(render_q.file_name))
|
||||
|
|
|
|||
|
|
@ -47,10 +47,8 @@ class FlameAppFramework(object):
|
|||
def setdefault(self, k, default=None):
|
||||
return self.master[self.name].setdefault(k, default)
|
||||
|
||||
def pop(self, k, v=object()):
|
||||
if v is object():
|
||||
return self.master[self.name].pop(k)
|
||||
return self.master[self.name].pop(k, v)
|
||||
def pop(self, *args, **kwargs):
|
||||
return self.master[self.name].pop(*args, **kwargs)
|
||||
|
||||
def update(self, mapping=(), **kwargs):
|
||||
self.master[self.name].update(mapping, **kwargs)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0"?>
|
||||
<preset version="9">
|
||||
<type>sequence</type>
|
||||
<comment>Creates a 8-bit Jpeg file per segment. </comment>
|
||||
<sequence>
|
||||
<fileType>NONE</fileType>
|
||||
<namePattern></namePattern>
|
||||
<composition><name></composition>
|
||||
<includeVideo>True</includeVideo>
|
||||
<exportVideo>True</exportVideo>
|
||||
<videoMedia>
|
||||
<mediaFileType>image</mediaFileType>
|
||||
<commit>FX</commit>
|
||||
<flatten>NoChange</flatten>
|
||||
<exportHandles>False</exportHandles>
|
||||
<nbHandles>10</nbHandles>
|
||||
</videoMedia>
|
||||
<includeAudio>True</includeAudio>
|
||||
<exportAudio>False</exportAudio>
|
||||
<audioMedia>
|
||||
<mediaFileType>audio</mediaFileType>
|
||||
<commit>FX</commit>
|
||||
<flatten>FlattenTracks</flatten>
|
||||
<exportHandles>True</exportHandles>
|
||||
<nbHandles>10</nbHandles>
|
||||
</audioMedia>
|
||||
</sequence>
|
||||
<video>
|
||||
<fileType>Jpeg</fileType>
|
||||
<codec>923688</codec>
|
||||
<codecProfile></codecProfile>
|
||||
<namePattern><segment name></namePattern>
|
||||
<compressionQuality>100</compressionQuality>
|
||||
<transferCharacteristic>2</transferCharacteristic>
|
||||
<colorimetricSpecification>4</colorimetricSpecification>
|
||||
<includeAlpha>False</includeAlpha>
|
||||
<overwriteWithVersions>False</overwriteWithVersions>
|
||||
<posterFrame>True</posterFrame>
|
||||
<useFrameAsPoster>1</useFrameAsPoster>
|
||||
<resize>
|
||||
<resizeType>fit</resizeType>
|
||||
<resizeFilter>lanczos</resizeFilter>
|
||||
<width>1920</width>
|
||||
<height>1080</height>
|
||||
<bitsPerChannel>8</bitsPerChannel>
|
||||
<numChannels>3</numChannels>
|
||||
<floatingPoint>False</floatingPoint>
|
||||
<bigEndian>True</bigEndian>
|
||||
<pixelRatio>1</pixelRatio>
|
||||
<scanFormat>P</scanFormat>
|
||||
</resize>
|
||||
</video>
|
||||
<name>
|
||||
<framePadding>4</framePadding>
|
||||
<startFrame>1</startFrame>
|
||||
<frameIndex>2</frameIndex>
|
||||
</name>
|
||||
</preset>
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
<?xml version="1.0"?>
|
||||
<preset version="10">
|
||||
<type>sequence</type>
|
||||
<comment>Create MOV H264 files per segment with thumbnail</comment>
|
||||
<sequence>
|
||||
<fileType>NONE</fileType>
|
||||
<namePattern></namePattern>
|
||||
<composition><name></composition>
|
||||
<includeVideo>True</includeVideo>
|
||||
<exportVideo>True</exportVideo>
|
||||
<videoMedia>
|
||||
<mediaFileType>movie</mediaFileType>
|
||||
<commit>FX</commit>
|
||||
<flatten>FlattenTracks</flatten>
|
||||
<exportHandles>True</exportHandles>
|
||||
<nbHandles>5</nbHandles>
|
||||
</videoMedia>
|
||||
<includeAudio>True</includeAudio>
|
||||
<exportAudio>False</exportAudio>
|
||||
<audioMedia>
|
||||
<mediaFileType>audio</mediaFileType>
|
||||
<commit>Original</commit>
|
||||
<flatten>NoChange</flatten>
|
||||
<exportHandles>True</exportHandles>
|
||||
<nbHandles>5</nbHandles>
|
||||
</audioMedia>
|
||||
</sequence>
|
||||
<movie>
|
||||
<fileType>QuickTime</fileType>
|
||||
<namePattern><segment name></namePattern>
|
||||
<yuvHeadroom>0</yuvHeadroom>
|
||||
<yuvColourSpace>PCS_709</yuvColourSpace>
|
||||
<operationalPattern>None</operationalPattern>
|
||||
<companyName>Autodesk</companyName>
|
||||
<productName>Flame</productName>
|
||||
<versionName>2021</versionName>
|
||||
</movie>
|
||||
<video>
|
||||
<fileType>QuickTime</fileType>
|
||||
<codec>33622016</codec>
|
||||
<codecProfile>
|
||||
<rootPath>/opt/Autodesk/mediaconverter/</rootPath>
|
||||
<targetVersion>2021</targetVersion>
|
||||
<pathSuffix>/profiles/.33622016/HDTV_720p_8Mbits.cdxprof</pathSuffix>
|
||||
</codecProfile>
|
||||
<namePattern><segment name>_<video codec></namePattern>
|
||||
<compressionQuality>50</compressionQuality>
|
||||
<transferCharacteristic>2</transferCharacteristic>
|
||||
<colorimetricSpecification>4</colorimetricSpecification>
|
||||
<includeAlpha>False</includeAlpha>
|
||||
<overwriteWithVersions>False</overwriteWithVersions>
|
||||
<posterFrame>False</posterFrame>
|
||||
<useFrameAsPoster>1</useFrameAsPoster>
|
||||
<resize>
|
||||
<resizeType>fit</resizeType>
|
||||
<resizeFilter>gaussian</resizeFilter>
|
||||
<width>1920</width>
|
||||
<height>1080</height>
|
||||
<bitsPerChannel>8</bitsPerChannel>
|
||||
<numChannels>3</numChannels>
|
||||
<floatingPoint>False</floatingPoint>
|
||||
<bigEndian>True</bigEndian>
|
||||
<pixelRatio>1</pixelRatio>
|
||||
<scanFormat>P</scanFormat>
|
||||
</resize>
|
||||
</video>
|
||||
<name>
|
||||
<framePadding>4</framePadding>
|
||||
<startFrame>1</startFrame>
|
||||
<frameIndex>2</frameIndex>
|
||||
</name>
|
||||
</preset>
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
import os
|
||||
import io
|
||||
import ConfigParser as CP
|
||||
from xml.etree import ElementTree as ET
|
||||
from contextlib import contextmanager
|
||||
|
||||
PLUGIN_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
EXPORT_PRESETS_DIR = os.path.join(PLUGIN_DIR, "export_preset")
|
||||
|
||||
CONFIG_DIR = os.path.join(os.path.expanduser(
|
||||
"~/.openpype"), "openpype_flame_to_ftrack")
|
||||
|
||||
|
||||
@contextmanager
|
||||
def make_temp_dir():
|
||||
import tempfile
|
||||
|
||||
try:
|
||||
dirpath = tempfile.mkdtemp()
|
||||
|
||||
yield dirpath
|
||||
|
||||
except IOError as _error:
|
||||
raise IOError("Not able to create temp dir file: {}".format(_error))
|
||||
|
||||
finally:
|
||||
pass
|
||||
|
||||
|
||||
@contextmanager
|
||||
def get_config(section=None):
|
||||
cfg_file_path = os.path.join(CONFIG_DIR, "settings.ini")
|
||||
|
||||
# create config dir
|
||||
if not os.path.exists(CONFIG_DIR):
|
||||
print("making dirs at: `{}`".format(CONFIG_DIR))
|
||||
os.makedirs(CONFIG_DIR, mode=0o777)
|
||||
|
||||
# write default data to settings.ini
|
||||
if not os.path.exists(cfg_file_path):
|
||||
default_cfg = cfg_default()
|
||||
config = CP.RawConfigParser()
|
||||
config.readfp(io.BytesIO(default_cfg))
|
||||
with open(cfg_file_path, 'wb') as cfg_file:
|
||||
config.write(cfg_file)
|
||||
|
||||
try:
|
||||
config = CP.RawConfigParser()
|
||||
config.read(cfg_file_path)
|
||||
if section:
|
||||
_cfg_data = {
|
||||
k: v
|
||||
for s in config.sections()
|
||||
for k, v in config.items(s)
|
||||
if s == section
|
||||
}
|
||||
else:
|
||||
_cfg_data = {s: dict(config.items(s)) for s in config.sections()}
|
||||
|
||||
yield _cfg_data
|
||||
|
||||
except IOError as _error:
|
||||
raise IOError('Not able to read settings.ini file: {}'.format(_error))
|
||||
|
||||
finally:
|
||||
pass
|
||||
|
||||
|
||||
def set_config(cfg_data, section=None):
|
||||
cfg_file_path = os.path.join(CONFIG_DIR, "settings.ini")
|
||||
|
||||
config = CP.RawConfigParser()
|
||||
config.read(cfg_file_path)
|
||||
|
||||
try:
|
||||
if not section:
|
||||
for section in cfg_data:
|
||||
for key, value in cfg_data[section].items():
|
||||
config.set(section, key, value)
|
||||
else:
|
||||
for key, value in cfg_data.items():
|
||||
config.set(section, key, value)
|
||||
|
||||
with open(cfg_file_path, 'wb') as cfg_file:
|
||||
config.write(cfg_file)
|
||||
|
||||
except IOError as _error:
|
||||
raise IOError('Not able to write settings.ini file: {}'.format(_error))
|
||||
|
||||
|
||||
def cfg_default():
|
||||
return """
|
||||
[main]
|
||||
workfile_start_frame = 1001
|
||||
shot_handles = 0
|
||||
shot_name_template = {sequence}_{shot}
|
||||
hierarchy_template = shots[Folder]/{sequence}[Sequence]
|
||||
create_task_type = Compositing
|
||||
"""
|
||||
|
||||
|
||||
def configure_preset(file_path, data):
|
||||
split_fp = os.path.splitext(file_path)
|
||||
new_file_path = split_fp[0] + "_tmp" + split_fp[-1]
|
||||
with open(file_path, "r") as datafile:
|
||||
tree = ET.parse(datafile)
|
||||
for key, value in data.items():
|
||||
for element in tree.findall(".//{}".format(key)):
|
||||
print(element)
|
||||
element.text = str(value)
|
||||
tree.write(new_file_path)
|
||||
|
||||
return new_file_path
|
||||
|
||||
|
||||
def export_thumbnail(sequence, tempdir_path, data):
|
||||
import flame
|
||||
export_preset = os.path.join(
|
||||
EXPORT_PRESETS_DIR,
|
||||
"openpype_seg_thumbnails_jpg.xml"
|
||||
)
|
||||
new_path = configure_preset(export_preset, data)
|
||||
poster_frame_exporter = flame.PyExporter()
|
||||
poster_frame_exporter.foreground = True
|
||||
poster_frame_exporter.export(sequence, new_path, tempdir_path)
|
||||
|
||||
|
||||
def export_video(sequence, tempdir_path, data):
|
||||
import flame
|
||||
export_preset = os.path.join(
|
||||
EXPORT_PRESETS_DIR,
|
||||
"openpype_seg_video_h264.xml"
|
||||
)
|
||||
new_path = configure_preset(export_preset, data)
|
||||
poster_frame_exporter = flame.PyExporter()
|
||||
poster_frame_exporter.foreground = True
|
||||
poster_frame_exporter.export(sequence, new_path, tempdir_path)
|
||||
|
||||
|
||||
def timecode_to_frames(timecode, framerate):
|
||||
def _seconds(value):
|
||||
if isinstance(value, str):
|
||||
_zip_ft = zip((3600, 60, 1, 1 / framerate), value.split(':'))
|
||||
return sum(f * float(t) for f, t in _zip_ft)
|
||||
elif isinstance(value, (int, float)):
|
||||
return value / framerate
|
||||
return 0
|
||||
|
||||
def _frames(seconds):
|
||||
return seconds * framerate
|
||||
|
||||
def tc_to_frames(_timecode, start=None):
|
||||
return _frames(_seconds(_timecode) - _seconds(start))
|
||||
|
||||
if '+' in timecode:
|
||||
timecode = timecode.replace('+', ':')
|
||||
elif '#' in timecode:
|
||||
timecode = timecode.replace('#', ':')
|
||||
|
||||
frames = int(round(tc_to_frames(timecode, start='00:00:00:00')))
|
||||
|
||||
return frames
|
||||
|
|
@ -0,0 +1,448 @@
|
|||
import os
|
||||
import sys
|
||||
import six
|
||||
import re
|
||||
import json
|
||||
|
||||
import app_utils
|
||||
|
||||
# Fill following constants or set them via environment variable
|
||||
FTRACK_MODULE_PATH = None
|
||||
FTRACK_API_KEY = None
|
||||
FTRACK_API_USER = None
|
||||
FTRACK_SERVER = None
|
||||
|
||||
|
||||
def import_ftrack_api():
|
||||
try:
|
||||
import ftrack_api
|
||||
return ftrack_api
|
||||
except ImportError:
|
||||
import sys
|
||||
ftrk_m_p = FTRACK_MODULE_PATH or os.getenv("FTRACK_MODULE_PATH")
|
||||
sys.path.append(ftrk_m_p)
|
||||
import ftrack_api
|
||||
return ftrack_api
|
||||
|
||||
|
||||
def get_ftrack_session():
|
||||
import os
|
||||
ftrack_api = import_ftrack_api()
|
||||
|
||||
# fill your own credentials
|
||||
url = FTRACK_SERVER or os.getenv("FTRACK_SERVER") or ""
|
||||
user = FTRACK_API_USER or os.getenv("FTRACK_API_USER") or ""
|
||||
api = FTRACK_API_KEY or os.getenv("FTRACK_API_KEY") or ""
|
||||
|
||||
first_validation = True
|
||||
if not user:
|
||||
print('- Ftrack Username is not set')
|
||||
first_validation = False
|
||||
if not api:
|
||||
print('- Ftrack API key is not set')
|
||||
first_validation = False
|
||||
if not first_validation:
|
||||
return False
|
||||
|
||||
try:
|
||||
return ftrack_api.Session(
|
||||
server_url=url,
|
||||
api_user=user,
|
||||
api_key=api
|
||||
)
|
||||
except Exception as _e:
|
||||
print("Can't log into Ftrack with used credentials: {}".format(_e))
|
||||
ftrack_cred = {
|
||||
'Ftrack server': str(url),
|
||||
'Username': str(user),
|
||||
'API key': str(api),
|
||||
}
|
||||
|
||||
item_lens = [len(key) + 1 for key in ftrack_cred]
|
||||
justify_len = max(*item_lens)
|
||||
for key, value in ftrack_cred.items():
|
||||
print('{} {}'.format((key + ':').ljust(justify_len, ' '), value))
|
||||
return False
|
||||
|
||||
|
||||
def get_project_task_types(project_entity):
|
||||
tasks = {}
|
||||
proj_template = project_entity['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
|
||||
|
||||
|
||||
class FtrackComponentCreator:
|
||||
default_location = "ftrack.server"
|
||||
ftrack_locations = {}
|
||||
thumbnails = []
|
||||
videos = []
|
||||
temp_dir = None
|
||||
|
||||
def __init__(self, session):
|
||||
self.session = session
|
||||
self._get_ftrack_location()
|
||||
|
||||
def generate_temp_data(self, selection, change_preset_data):
|
||||
with app_utils.make_temp_dir() as tempdir_path:
|
||||
for seq in selection:
|
||||
app_utils.export_thumbnail(
|
||||
seq, tempdir_path, change_preset_data)
|
||||
app_utils.export_video(seq, tempdir_path, change_preset_data)
|
||||
|
||||
return tempdir_path
|
||||
|
||||
def collect_generated_data(self, tempdir_path):
|
||||
temp_files = os.listdir(tempdir_path)
|
||||
self.thumbnails = [f for f in temp_files if "jpg" in f]
|
||||
self.videos = [f for f in temp_files if "mov" in f]
|
||||
self.temp_dir = tempdir_path
|
||||
|
||||
def get_thumb_path(self, shot_name):
|
||||
# get component files
|
||||
thumb_f = next((f for f in self.thumbnails if shot_name in f), None)
|
||||
return os.path.join(self.temp_dir, thumb_f)
|
||||
|
||||
def get_video_path(self, shot_name):
|
||||
# get component files
|
||||
video_f = next((f for f in self.videos if shot_name in f), None)
|
||||
return os.path.join(self.temp_dir, video_f)
|
||||
|
||||
def close(self):
|
||||
self.ftrack_locations = {}
|
||||
self.session = None
|
||||
|
||||
def create_comonent(self, shot_entity, data, assetversion_entity=None):
|
||||
self.shot_entity = shot_entity
|
||||
location = self._get_ftrack_location()
|
||||
|
||||
file_path = data["file_path"]
|
||||
|
||||
# get extension
|
||||
file = os.path.basename(file_path)
|
||||
_n, ext = os.path.splitext(file)
|
||||
|
||||
name = "ftrackreview-mp4" if "mov" in ext else "thumbnail"
|
||||
|
||||
component_data = {
|
||||
"name": name,
|
||||
"file_path": file_path,
|
||||
"file_type": ext,
|
||||
"location": location
|
||||
}
|
||||
|
||||
if name == "ftrackreview-mp4":
|
||||
duration = data["duration"]
|
||||
handles = data["handles"]
|
||||
fps = data["fps"]
|
||||
component_data["metadata"] = {
|
||||
'ftr_meta': json.dumps({
|
||||
'frameIn': int(0),
|
||||
'frameOut': int(duration + (handles * 2)),
|
||||
'frameRate': float(fps)
|
||||
})
|
||||
}
|
||||
if not assetversion_entity:
|
||||
# get assettype entity from session
|
||||
assettype_entity = self._get_assettype({"short": "reference"})
|
||||
|
||||
# get or create asset entity from session
|
||||
asset_entity = self._get_asset({
|
||||
"name": "plateReference",
|
||||
"type": assettype_entity,
|
||||
"parent": self.shot_entity
|
||||
})
|
||||
|
||||
# get or create assetversion entity from session
|
||||
assetversion_entity = self._get_assetversion({
|
||||
"version": 0,
|
||||
"asset": asset_entity
|
||||
})
|
||||
|
||||
# get or create component entity
|
||||
self._set_component(component_data, {
|
||||
"name": name,
|
||||
"version": assetversion_entity,
|
||||
})
|
||||
|
||||
return assetversion_entity
|
||||
|
||||
def _overwrite_members(self, entity, data):
|
||||
origin_location = self._get_ftrack_location("ftrack.origin")
|
||||
location = data.pop("location")
|
||||
|
||||
self._remove_component_from_location(entity, location)
|
||||
|
||||
entity["file_type"] = data["file_type"]
|
||||
|
||||
try:
|
||||
origin_location.add_component(
|
||||
entity, data["file_path"]
|
||||
)
|
||||
# Add components to location.
|
||||
location.add_component(
|
||||
entity, origin_location, recursive=True)
|
||||
except Exception as __e:
|
||||
print("Error: {}".format(__e))
|
||||
self._remove_component_from_location(entity, origin_location)
|
||||
origin_location.add_component(
|
||||
entity, data["file_path"]
|
||||
)
|
||||
# Add components to location.
|
||||
location.add_component(
|
||||
entity, origin_location, recursive=True)
|
||||
|
||||
def _remove_component_from_location(self, entity, location):
|
||||
print(location)
|
||||
# Removing existing members from location
|
||||
components = list(entity.get("members", []))
|
||||
components += [entity]
|
||||
for component in components:
|
||||
for loc in component.get("component_locations", []):
|
||||
if location["id"] == loc["location_id"]:
|
||||
print("<< Removing component: {}".format(component))
|
||||
location.remove_component(
|
||||
component, recursive=False
|
||||
)
|
||||
|
||||
# Deleting existing members on component entity
|
||||
for member in entity.get("members", []):
|
||||
self.session.delete(member)
|
||||
print("<< Deleting member: {}".format(member))
|
||||
del(member)
|
||||
|
||||
self._commit()
|
||||
|
||||
# Reset members in memory
|
||||
if "members" in entity.keys():
|
||||
entity["members"] = []
|
||||
|
||||
def _get_assettype(self, data):
|
||||
return self.session.query(
|
||||
self._query("AssetType", data)).first()
|
||||
|
||||
def _set_component(self, comp_data, base_data):
|
||||
component_metadata = comp_data.pop("metadata", {})
|
||||
|
||||
component_entity = self.session.query(
|
||||
self._query("Component", base_data)
|
||||
).first()
|
||||
|
||||
if component_entity:
|
||||
# overwrite existing members in component enity
|
||||
# - get data for member from `ftrack.origin` location
|
||||
self._overwrite_members(component_entity, comp_data)
|
||||
|
||||
# Adding metadata
|
||||
existing_component_metadata = component_entity["metadata"]
|
||||
existing_component_metadata.update(component_metadata)
|
||||
component_entity["metadata"] = existing_component_metadata
|
||||
return
|
||||
|
||||
assetversion_entity = base_data["version"]
|
||||
location = comp_data.pop("location")
|
||||
|
||||
component_entity = assetversion_entity.create_component(
|
||||
comp_data["file_path"],
|
||||
data=comp_data,
|
||||
location=location
|
||||
)
|
||||
|
||||
# Adding metadata
|
||||
existing_component_metadata = component_entity["metadata"]
|
||||
existing_component_metadata.update(component_metadata)
|
||||
component_entity["metadata"] = existing_component_metadata
|
||||
|
||||
if comp_data["name"] == "thumbnail":
|
||||
self.shot_entity["thumbnail_id"] = component_entity["id"]
|
||||
assetversion_entity["thumbnail_id"] = component_entity["id"]
|
||||
|
||||
self._commit()
|
||||
|
||||
def _get_asset(self, data):
|
||||
# first find already created
|
||||
asset_entity = self.session.query(
|
||||
self._query("Asset", data)
|
||||
).first()
|
||||
|
||||
if asset_entity:
|
||||
return asset_entity
|
||||
|
||||
asset_entity = self.session.create("Asset", data)
|
||||
|
||||
# _commit if created
|
||||
self._commit()
|
||||
|
||||
return asset_entity
|
||||
|
||||
def _get_assetversion(self, data):
|
||||
assetversion_entity = self.session.query(
|
||||
self._query("AssetVersion", data)
|
||||
).first()
|
||||
|
||||
if assetversion_entity:
|
||||
return assetversion_entity
|
||||
|
||||
assetversion_entity = self.session.create("AssetVersion", data)
|
||||
|
||||
# _commit if created
|
||||
self._commit()
|
||||
|
||||
return assetversion_entity
|
||||
|
||||
def _commit(self):
|
||||
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 _get_ftrack_location(self, name=None):
|
||||
name = name or self.default_location
|
||||
|
||||
if name in self.ftrack_locations:
|
||||
return self.ftrack_locations[name]
|
||||
|
||||
location = self.session.query(
|
||||
'Location where name is "{}"'.format(name)
|
||||
).one()
|
||||
self.ftrack_locations[name] = location
|
||||
return location
|
||||
|
||||
def _query(self, entitytype, data):
|
||||
""" Generate a query expression from data supplied.
|
||||
|
||||
If a value is not a string, we'll add the id of the entity to the
|
||||
query.
|
||||
|
||||
Args:
|
||||
entitytype (str): The type of entity to query.
|
||||
data (dict): The data to identify the entity.
|
||||
exclusions (list): All keys to exclude from the query.
|
||||
|
||||
Returns:
|
||||
str: String query to use with "session.query"
|
||||
"""
|
||||
queries = []
|
||||
if sys.version_info[0] < 3:
|
||||
for key, value in data.items():
|
||||
if not isinstance(value, (str, int)):
|
||||
print("value: {}".format(value))
|
||||
if "id" in value.keys():
|
||||
queries.append(
|
||||
"{0}.id is \"{1}\"".format(key, value["id"])
|
||||
)
|
||||
else:
|
||||
queries.append("{0} is \"{1}\"".format(key, value))
|
||||
else:
|
||||
for key, value in data.items():
|
||||
if not isinstance(value, (str, int)):
|
||||
print("value: {}".format(value))
|
||||
if "id" in value.keys():
|
||||
queries.append(
|
||||
"{0}.id is \"{1}\"".format(key, value["id"])
|
||||
)
|
||||
else:
|
||||
queries.append("{0} is \"{1}\"".format(key, value))
|
||||
|
||||
query = (
|
||||
"select id from " + entitytype + " where " + " and ".join(queries)
|
||||
)
|
||||
print(query)
|
||||
return query
|
||||
|
||||
|
||||
class FtrackEntityOperator:
|
||||
def __init__(self, session, project_entity):
|
||||
self.session = session
|
||||
self.project_entity = project_entity
|
||||
|
||||
def commit(self):
|
||||
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 create_ftrack_entity(self, session, type, name, parent=None):
|
||||
parent = parent or self.project_entity
|
||||
entity = session.create(type, {
|
||||
'name': name,
|
||||
'parent': parent
|
||||
})
|
||||
try:
|
||||
session.commit()
|
||||
except Exception:
|
||||
tp, value, tb = sys.exc_info()
|
||||
session.rollback()
|
||||
session._configure_locations()
|
||||
six.reraise(tp, value, tb)
|
||||
return entity
|
||||
|
||||
def get_ftrack_entity(self, session, type, name, parent):
|
||||
query = '{} where name is "{}" and project_id is "{}"'.format(
|
||||
type, name, self.project_entity["id"])
|
||||
|
||||
try:
|
||||
entity = session.query(query).one()
|
||||
except Exception:
|
||||
entity = None
|
||||
|
||||
# if entity doesnt exist then create one
|
||||
if not entity:
|
||||
entity = self.create_ftrack_entity(
|
||||
session,
|
||||
type,
|
||||
name,
|
||||
parent
|
||||
)
|
||||
|
||||
return entity
|
||||
|
||||
def create_parents(self, template):
|
||||
parents = []
|
||||
t_split = template.split("/")
|
||||
replace_patern = re.compile(r"(\[.*\])")
|
||||
type_patern = re.compile(r"\[(.*)\]")
|
||||
|
||||
for t_s in t_split:
|
||||
match_type = type_patern.findall(t_s)
|
||||
if not match_type:
|
||||
raise Exception((
|
||||
"Missing correct type flag in : {}"
|
||||
"/n Example: name[Type]").format(
|
||||
t_s)
|
||||
)
|
||||
new_name = re.sub(replace_patern, "", t_s)
|
||||
f_type = match_type.pop()
|
||||
|
||||
parents.append((new_name, f_type))
|
||||
|
||||
return parents
|
||||
|
||||
def create_task(self, task_type, task_types, parent):
|
||||
existing_task = [
|
||||
child for child in parent['children']
|
||||
if child.entity_type.lower() == 'task'
|
||||
if child['name'].lower() in task_type.lower()
|
||||
]
|
||||
|
||||
if existing_task:
|
||||
return existing_task.pop()
|
||||
|
||||
task = self.session.create('Task', {
|
||||
"name": task_type.lower(),
|
||||
"parent": parent
|
||||
})
|
||||
task["type"] = task_types[task_type]
|
||||
|
||||
return task
|
||||
|
|
@ -0,0 +1,524 @@
|
|||
from PySide2 import QtWidgets, QtCore
|
||||
|
||||
import uiwidgets
|
||||
import app_utils
|
||||
import ftrack_lib
|
||||
|
||||
|
||||
def clear_inner_modules():
|
||||
import sys
|
||||
|
||||
if "ftrack_lib" in sys.modules.keys():
|
||||
del sys.modules["ftrack_lib"]
|
||||
print("Ftrack Lib module removed from sys.modules")
|
||||
|
||||
if "app_utils" in sys.modules.keys():
|
||||
del sys.modules["app_utils"]
|
||||
print("app_utils module removed from sys.modules")
|
||||
|
||||
if "uiwidgets" in sys.modules.keys():
|
||||
del sys.modules["uiwidgets"]
|
||||
print("uiwidgets module removed from sys.modules")
|
||||
|
||||
|
||||
class MainWindow(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, klass, *args, **kwargs):
|
||||
super(MainWindow, self).__init__(*args, **kwargs)
|
||||
self.panel_class = klass
|
||||
|
||||
def closeEvent(self, event):
|
||||
# clear all temp data
|
||||
print("Removing temp data")
|
||||
self.panel_class.clear_temp_data()
|
||||
self.panel_class.close()
|
||||
clear_inner_modules()
|
||||
# now the panel can be closed
|
||||
event.accept()
|
||||
|
||||
|
||||
class FlameToFtrackPanel(object):
|
||||
session = None
|
||||
temp_data_dir = None
|
||||
processed_components = []
|
||||
project_entity = None
|
||||
task_types = {}
|
||||
all_task_types = {}
|
||||
|
||||
# TreeWidget
|
||||
columns = {
|
||||
"Sequence name": {
|
||||
"columnWidth": 200,
|
||||
"order": 0
|
||||
},
|
||||
"Shot name": {
|
||||
"columnWidth": 200,
|
||||
"order": 1
|
||||
},
|
||||
"Clip duration": {
|
||||
"columnWidth": 100,
|
||||
"order": 2
|
||||
},
|
||||
"Shot description": {
|
||||
"columnWidth": 500,
|
||||
"order": 3
|
||||
},
|
||||
"Task description": {
|
||||
"columnWidth": 500,
|
||||
"order": 4
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, selection):
|
||||
print(selection)
|
||||
|
||||
self.session = ftrack_lib.get_ftrack_session()
|
||||
self.selection = selection
|
||||
self.window = MainWindow(self)
|
||||
|
||||
# creating ui
|
||||
self.window.setMinimumSize(1500, 600)
|
||||
self.window.setWindowTitle('Sequence Shots to Ftrack')
|
||||
self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
|
||||
self.window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
||||
self.window.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
self.window.setStyleSheet('background-color: #313131')
|
||||
|
||||
self._create_project_widget()
|
||||
self._create_tree_widget()
|
||||
self._set_sequence_params()
|
||||
self._generate_widgets()
|
||||
self._generate_layouts()
|
||||
self._timeline_info()
|
||||
self._fix_resolution()
|
||||
|
||||
self.window.show()
|
||||
|
||||
def _generate_widgets(self):
|
||||
with app_utils.get_config("main") as cfg_data:
|
||||
cfg_d = cfg_data
|
||||
|
||||
self._create_task_type_widget(cfg_d)
|
||||
|
||||
# input fields
|
||||
self.shot_name_label = uiwidgets.FlameLabel(
|
||||
'Shot name template', 'normal', self.window)
|
||||
self.shot_name_template_input = uiwidgets.FlameLineEdit(
|
||||
cfg_d["shot_name_template"], self.window)
|
||||
|
||||
self.hierarchy_label = uiwidgets.FlameLabel(
|
||||
'Parents template', 'normal', self.window)
|
||||
self.hierarchy_template_input = uiwidgets.FlameLineEdit(
|
||||
cfg_d["hierarchy_template"], self.window)
|
||||
|
||||
self.start_frame_label = uiwidgets.FlameLabel(
|
||||
'Workfile start frame', 'normal', self.window)
|
||||
self.start_frame_input = uiwidgets.FlameLineEdit(
|
||||
cfg_d["workfile_start_frame"], self.window)
|
||||
|
||||
self.handles_label = uiwidgets.FlameLabel(
|
||||
'Shot handles', 'normal', self.window)
|
||||
self.handles_input = uiwidgets.FlameLineEdit(
|
||||
cfg_d["shot_handles"], self.window)
|
||||
|
||||
self.width_label = uiwidgets.FlameLabel(
|
||||
'Sequence width', 'normal', self.window)
|
||||
self.width_input = uiwidgets.FlameLineEdit(
|
||||
str(self.seq_width), self.window)
|
||||
|
||||
self.height_label = uiwidgets.FlameLabel(
|
||||
'Sequence height', 'normal', self.window)
|
||||
self.height_input = uiwidgets.FlameLineEdit(
|
||||
str(self.seq_height), self.window)
|
||||
|
||||
self.pixel_aspect_label = uiwidgets.FlameLabel(
|
||||
'Pixel aspect ratio', 'normal', self.window)
|
||||
self.pixel_aspect_input = uiwidgets.FlameLineEdit(
|
||||
str(1.00), self.window)
|
||||
|
||||
self.fps_label = uiwidgets.FlameLabel(
|
||||
'Frame rate', 'normal', self.window)
|
||||
self.fps_input = uiwidgets.FlameLineEdit(
|
||||
str(self.fps), self.window)
|
||||
|
||||
# Button
|
||||
self.select_all_btn = uiwidgets.FlameButton(
|
||||
'Select All', self.select_all, self.window)
|
||||
|
||||
self.remove_temp_data_btn = uiwidgets.FlameButton(
|
||||
'Remove temp data', self.clear_temp_data, self.window)
|
||||
|
||||
self.ftrack_send_btn = uiwidgets.FlameButton(
|
||||
'Send to Ftrack', self._send_to_ftrack, self.window)
|
||||
|
||||
def _generate_layouts(self):
|
||||
# left props
|
||||
v_shift = 0
|
||||
prop_layout_l = QtWidgets.QGridLayout()
|
||||
prop_layout_l.setHorizontalSpacing(30)
|
||||
if self.project_selector_enabled:
|
||||
prop_layout_l.addWidget(self.project_select_label, v_shift, 0)
|
||||
prop_layout_l.addWidget(self.project_select_input, v_shift, 1)
|
||||
v_shift += 1
|
||||
prop_layout_l.addWidget(self.shot_name_label, (v_shift + 0), 0)
|
||||
prop_layout_l.addWidget(
|
||||
self.shot_name_template_input, (v_shift + 0), 1)
|
||||
prop_layout_l.addWidget(self.hierarchy_label, (v_shift + 1), 0)
|
||||
prop_layout_l.addWidget(
|
||||
self.hierarchy_template_input, (v_shift + 1), 1)
|
||||
prop_layout_l.addWidget(self.start_frame_label, (v_shift + 2), 0)
|
||||
prop_layout_l.addWidget(self.start_frame_input, (v_shift + 2), 1)
|
||||
prop_layout_l.addWidget(self.handles_label, (v_shift + 3), 0)
|
||||
prop_layout_l.addWidget(self.handles_input, (v_shift + 3), 1)
|
||||
prop_layout_l.addWidget(self.task_type_label, (v_shift + 4), 0)
|
||||
prop_layout_l.addWidget(
|
||||
self.task_type_input, (v_shift + 4), 1)
|
||||
|
||||
# right props
|
||||
prop_widget_r = QtWidgets.QWidget(self.window)
|
||||
prop_layout_r = QtWidgets.QGridLayout(prop_widget_r)
|
||||
prop_layout_r.setHorizontalSpacing(30)
|
||||
prop_layout_r.setAlignment(
|
||||
QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
|
||||
prop_layout_r.setContentsMargins(0, 0, 0, 0)
|
||||
prop_layout_r.addWidget(self.width_label, 1, 0)
|
||||
prop_layout_r.addWidget(self.width_input, 1, 1)
|
||||
prop_layout_r.addWidget(self.height_label, 2, 0)
|
||||
prop_layout_r.addWidget(self.height_input, 2, 1)
|
||||
prop_layout_r.addWidget(self.pixel_aspect_label, 3, 0)
|
||||
prop_layout_r.addWidget(self.pixel_aspect_input, 3, 1)
|
||||
prop_layout_r.addWidget(self.fps_label, 4, 0)
|
||||
prop_layout_r.addWidget(self.fps_input, 4, 1)
|
||||
|
||||
# prop layout
|
||||
prop_main_layout = QtWidgets.QHBoxLayout()
|
||||
prop_main_layout.addLayout(prop_layout_l, 1)
|
||||
prop_main_layout.addSpacing(20)
|
||||
prop_main_layout.addWidget(prop_widget_r, 1)
|
||||
|
||||
# buttons layout
|
||||
hbox = QtWidgets.QHBoxLayout()
|
||||
hbox.addWidget(self.remove_temp_data_btn)
|
||||
hbox.addWidget(self.select_all_btn)
|
||||
hbox.addWidget(self.ftrack_send_btn)
|
||||
|
||||
# put all layouts together
|
||||
main_frame = QtWidgets.QVBoxLayout(self.window)
|
||||
main_frame.setMargin(20)
|
||||
main_frame.addLayout(prop_main_layout)
|
||||
main_frame.addWidget(self.tree)
|
||||
main_frame.addLayout(hbox)
|
||||
|
||||
def _set_sequence_params(self):
|
||||
for select in self.selection:
|
||||
self.seq_height = select.height
|
||||
self.seq_width = select.width
|
||||
self.fps = float(str(select.frame_rate)[:-4])
|
||||
break
|
||||
|
||||
def _create_task_type_widget(self, cfg_d):
|
||||
print(self.project_entity)
|
||||
self.task_types = ftrack_lib.get_project_task_types(
|
||||
self.project_entity)
|
||||
|
||||
self.task_type_label = uiwidgets.FlameLabel(
|
||||
'Create Task (type)', 'normal', self.window)
|
||||
self.task_type_input = uiwidgets.FlamePushButtonMenu(
|
||||
cfg_d["create_task_type"], self.task_types.keys(), self.window)
|
||||
|
||||
def _create_project_widget(self):
|
||||
import flame
|
||||
# get project name from flame current project
|
||||
self.project_name = flame.project.current_project.name
|
||||
|
||||
# get project from ftrack -
|
||||
# ftrack project name has to be the same as flame project!
|
||||
query = 'Project where full_name is "{}"'.format(self.project_name)
|
||||
|
||||
# globally used variables
|
||||
self.project_entity = self.session.query(query).first()
|
||||
|
||||
self.project_selector_enabled = bool(not self.project_entity)
|
||||
|
||||
if self.project_selector_enabled:
|
||||
self.all_projects = self.session.query(
|
||||
"Project where status is active").all()
|
||||
self.project_entity = self.all_projects[0]
|
||||
project_names = [p["full_name"] for p in self.all_projects]
|
||||
self.all_task_types = {
|
||||
p["full_name"]: ftrack_lib.get_project_task_types(p).keys()
|
||||
for p in self.all_projects
|
||||
}
|
||||
self.project_select_label = uiwidgets.FlameLabel(
|
||||
'Select Ftrack project', 'normal', self.window)
|
||||
self.project_select_input = uiwidgets.FlamePushButtonMenu(
|
||||
self.project_entity["full_name"], project_names, self.window)
|
||||
self.project_select_input.selection_changed.connect(
|
||||
self._on_project_changed)
|
||||
|
||||
def _create_tree_widget(self):
|
||||
ordered_column_labels = self.columns.keys()
|
||||
for _name, _value in self.columns.items():
|
||||
ordered_column_labels.pop(_value["order"])
|
||||
ordered_column_labels.insert(_value["order"], _name)
|
||||
|
||||
self.tree = uiwidgets.FlameTreeWidget(
|
||||
ordered_column_labels, self.window)
|
||||
|
||||
# Allow multiple items in tree to be selected
|
||||
self.tree.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
|
||||
|
||||
# Set tree column width
|
||||
for _name, _val in self.columns.items():
|
||||
self.tree.setColumnWidth(
|
||||
_val["order"],
|
||||
_val["columnWidth"]
|
||||
)
|
||||
|
||||
# Prevent weird characters when shrinking tree columns
|
||||
self.tree.setTextElideMode(QtCore.Qt.ElideNone)
|
||||
|
||||
def _resolve_project_entity(self):
|
||||
if self.project_selector_enabled:
|
||||
selected_project_name = self.project_select_input.text()
|
||||
self.project_entity = next(
|
||||
(p for p in self.all_projects
|
||||
if p["full_name"] in selected_project_name),
|
||||
None
|
||||
)
|
||||
|
||||
def _save_ui_state_to_cfg(self):
|
||||
_cfg_data_back = {
|
||||
"shot_name_template": self.shot_name_template_input.text(),
|
||||
"workfile_start_frame": self.start_frame_input.text(),
|
||||
"shot_handles": self.handles_input.text(),
|
||||
"hierarchy_template": self.hierarchy_template_input.text(),
|
||||
"create_task_type": self.task_type_input.text()
|
||||
}
|
||||
|
||||
# add cfg data back to settings.ini
|
||||
app_utils.set_config(_cfg_data_back, "main")
|
||||
|
||||
def _send_to_ftrack(self):
|
||||
# resolve active project and add it to self.project_entity
|
||||
self._resolve_project_entity()
|
||||
self._save_ui_state_to_cfg()
|
||||
|
||||
# get hanldes from gui input
|
||||
handles = self.handles_input.text()
|
||||
|
||||
# get frame start from gui input
|
||||
frame_start = int(self.start_frame_input.text())
|
||||
|
||||
# get task type from gui input
|
||||
task_type = self.task_type_input.text()
|
||||
|
||||
# get resolution from gui inputs
|
||||
fps = self.fps_input.text()
|
||||
|
||||
entity_operator = ftrack_lib.FtrackEntityOperator(
|
||||
self.session, self.project_entity)
|
||||
component_creator = ftrack_lib.FtrackComponentCreator(self.session)
|
||||
|
||||
if not self.temp_data_dir:
|
||||
self.window.hide()
|
||||
self.temp_data_dir = component_creator.generate_temp_data(
|
||||
self.selection,
|
||||
{
|
||||
"nbHandles": handles
|
||||
}
|
||||
)
|
||||
self.window.show()
|
||||
|
||||
# collect generated files to list data for farther use
|
||||
component_creator.collect_generated_data(self.temp_data_dir)
|
||||
|
||||
# Get all selected items from treewidget
|
||||
for item in self.tree.selectedItems():
|
||||
# frame ranges
|
||||
frame_duration = int(item.text(2))
|
||||
frame_end = frame_start + frame_duration
|
||||
|
||||
# description
|
||||
shot_description = item.text(3)
|
||||
task_description = item.text(4)
|
||||
|
||||
# other
|
||||
sequence_name = item.text(0)
|
||||
shot_name = item.text(1)
|
||||
|
||||
thumb_fp = component_creator.get_thumb_path(shot_name)
|
||||
video_fp = component_creator.get_video_path(shot_name)
|
||||
|
||||
print("processed comps: {}".format(self.processed_components))
|
||||
print("processed thumb_fp: {}".format(thumb_fp))
|
||||
|
||||
processed = False
|
||||
if thumb_fp not in self.processed_components:
|
||||
self.processed_components.append(thumb_fp)
|
||||
else:
|
||||
processed = True
|
||||
|
||||
print("processed: {}".format(processed))
|
||||
|
||||
# populate full shot info
|
||||
shot_attributes = {
|
||||
"sequence": sequence_name,
|
||||
"shot": shot_name,
|
||||
"task": task_type
|
||||
}
|
||||
|
||||
# format shot name template
|
||||
_shot_name = self.shot_name_template_input.text().format(
|
||||
**shot_attributes)
|
||||
|
||||
# format hierarchy template
|
||||
_hierarchy_text = self.hierarchy_template_input.text().format(
|
||||
**shot_attributes)
|
||||
print(_hierarchy_text)
|
||||
|
||||
# solve parents
|
||||
parents = entity_operator.create_parents(_hierarchy_text)
|
||||
print(parents)
|
||||
|
||||
# obtain shot parents entities
|
||||
_parent = None
|
||||
for _name, _type in parents:
|
||||
p_entity = entity_operator.get_ftrack_entity(
|
||||
self.session,
|
||||
_type,
|
||||
_name,
|
||||
_parent
|
||||
)
|
||||
print(p_entity)
|
||||
_parent = p_entity
|
||||
|
||||
# obtain shot ftrack entity
|
||||
f_s_entity = entity_operator.get_ftrack_entity(
|
||||
self.session,
|
||||
"Shot",
|
||||
_shot_name,
|
||||
_parent
|
||||
)
|
||||
print("Shot entity is: {}".format(f_s_entity))
|
||||
|
||||
if not processed:
|
||||
# first create thumbnail and get version entity
|
||||
assetversion_entity = component_creator.create_comonent(
|
||||
f_s_entity, {
|
||||
"file_path": thumb_fp
|
||||
}
|
||||
)
|
||||
|
||||
# secondly add video to version entity
|
||||
component_creator.create_comonent(
|
||||
f_s_entity, {
|
||||
"file_path": video_fp,
|
||||
"duration": frame_duration,
|
||||
"handles": int(handles),
|
||||
"fps": float(fps)
|
||||
}, assetversion_entity
|
||||
)
|
||||
|
||||
# create custom attributtes
|
||||
custom_attrs = {
|
||||
"frameStart": frame_start,
|
||||
"frameEnd": frame_end,
|
||||
"handleStart": int(handles),
|
||||
"handleEnd": int(handles),
|
||||
"resolutionWidth": int(self.width_input.text()),
|
||||
"resolutionHeight": int(self.height_input.text()),
|
||||
"pixelAspect": float(self.pixel_aspect_input.text()),
|
||||
"fps": float(fps)
|
||||
}
|
||||
|
||||
# update custom attributes on shot entity
|
||||
for key in custom_attrs:
|
||||
f_s_entity['custom_attributes'][key] = custom_attrs[key]
|
||||
|
||||
task_entity = entity_operator.create_task(
|
||||
task_type, self.task_types, f_s_entity)
|
||||
|
||||
# Create notes.
|
||||
user = self.session.query(
|
||||
"User where username is \"{}\"".format(self.session.api_user)
|
||||
).first()
|
||||
|
||||
f_s_entity.create_note(shot_description, author=user)
|
||||
|
||||
if task_description:
|
||||
task_entity.create_note(task_description, user)
|
||||
|
||||
entity_operator.commit()
|
||||
|
||||
component_creator.close()
|
||||
|
||||
def _fix_resolution(self):
|
||||
# Center window in linux
|
||||
resolution = QtWidgets.QDesktopWidget().screenGeometry()
|
||||
self.window.move(
|
||||
(resolution.width() / 2) - (self.window.frameSize().width() / 2),
|
||||
(resolution.height() / 2) - (self.window.frameSize().height() / 2))
|
||||
|
||||
def _on_project_changed(self):
|
||||
task_types = self.all_task_types[self.project_name]
|
||||
self.task_type_input.set_menu_options(task_types)
|
||||
|
||||
def _timeline_info(self):
|
||||
# identificar as informacoes dos segmentos na timeline
|
||||
for sequence in self.selection:
|
||||
frame_rate = float(str(sequence.frame_rate)[:-4])
|
||||
for ver in sequence.versions:
|
||||
for tracks in ver.tracks:
|
||||
for segment in tracks.segments:
|
||||
print(segment.attributes)
|
||||
if str(segment.name)[1:-1] == "":
|
||||
continue
|
||||
# get clip frame duration
|
||||
record_duration = str(segment.record_duration)[1:-1]
|
||||
clip_duration = app_utils.timecode_to_frames(
|
||||
record_duration, frame_rate)
|
||||
|
||||
# populate shot source metadata
|
||||
shot_description = ""
|
||||
for attr in ["tape_name", "source_name", "head",
|
||||
"tail", "file_path"]:
|
||||
if not hasattr(segment, attr):
|
||||
continue
|
||||
_value = getattr(segment, attr)
|
||||
_label = attr.replace("_", " ").capitalize()
|
||||
row = "{}: {}\n".format(_label, _value)
|
||||
shot_description += row
|
||||
|
||||
# Add timeline segment to tree
|
||||
QtWidgets.QTreeWidgetItem(self.tree, [
|
||||
str(sequence.name)[1:-1], # seq
|
||||
str(segment.name)[1:-1], # shot
|
||||
str(clip_duration), # clip duration
|
||||
shot_description, # shot description
|
||||
str(segment.comment)[1:-1] # task description
|
||||
]).setFlags(
|
||||
QtCore.Qt.ItemIsEditable
|
||||
| QtCore.Qt.ItemIsEnabled
|
||||
| QtCore.Qt.ItemIsSelectable
|
||||
)
|
||||
|
||||
# Select top item in tree
|
||||
self.tree.setCurrentItem(self.tree.topLevelItem(0))
|
||||
|
||||
def select_all(self, ):
|
||||
self.tree.selectAll()
|
||||
|
||||
def clear_temp_data(self):
|
||||
import shutil
|
||||
|
||||
self.processed_components = []
|
||||
|
||||
if self.temp_data_dir:
|
||||
shutil.rmtree(self.temp_data_dir)
|
||||
self.temp_data_dir = None
|
||||
print("All Temp data were destroied ...")
|
||||
|
||||
def close(self):
|
||||
self._save_ui_state_to_cfg()
|
||||
self.session.close()
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
from PySide2 import QtWidgets, QtCore
|
||||
|
||||
|
||||
class FlameLabel(QtWidgets.QLabel):
|
||||
"""
|
||||
Custom Qt Flame Label Widget
|
||||
|
||||
For different label looks set label_type as:
|
||||
'normal', 'background', or 'outline'
|
||||
|
||||
To use:
|
||||
|
||||
label = FlameLabel('Label Name', 'normal', window)
|
||||
"""
|
||||
|
||||
def __init__(self, label_name, label_type, parent_window, *args, **kwargs):
|
||||
super(FlameLabel, self).__init__(*args, **kwargs)
|
||||
|
||||
self.setText(label_name)
|
||||
self.setParent(parent_window)
|
||||
self.setMinimumSize(130, 28)
|
||||
self.setMaximumHeight(28)
|
||||
self.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
|
||||
# Set label stylesheet based on label_type
|
||||
|
||||
if label_type == 'normal':
|
||||
self.setStyleSheet(
|
||||
'QLabel {color: #9a9a9a; border-bottom: 1px inset #282828; font: 14px "Discreet"}' # noqa
|
||||
'QLabel:disabled {color: #6a6a6a}'
|
||||
)
|
||||
elif label_type == 'background':
|
||||
self.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.setStyleSheet(
|
||||
'color: #9a9a9a; background-color: #393939; font: 14px "Discreet"' # noqa
|
||||
)
|
||||
elif label_type == 'outline':
|
||||
self.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.setStyleSheet(
|
||||
'color: #9a9a9a; background-color: #212121; border: 1px solid #404040; font: 14px "Discreet"' # noqa
|
||||
)
|
||||
|
||||
|
||||
class FlameLineEdit(QtWidgets.QLineEdit):
|
||||
"""
|
||||
Custom Qt Flame Line Edit Widget
|
||||
|
||||
Main window should include this:
|
||||
window.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
|
||||
To use:
|
||||
|
||||
line_edit = FlameLineEdit('Some text here', window)
|
||||
"""
|
||||
|
||||
def __init__(self, text, parent_window, *args, **kwargs):
|
||||
super(FlameLineEdit, self).__init__(*args, **kwargs)
|
||||
|
||||
self.setText(text)
|
||||
self.setParent(parent_window)
|
||||
self.setMinimumHeight(28)
|
||||
self.setMinimumWidth(110)
|
||||
self.setStyleSheet(
|
||||
'QLineEdit {color: #9a9a9a; background-color: #373e47; selection-color: #262626; selection-background-color: #b8b1a7; font: 14px "Discreet"}' # noqa
|
||||
'QLineEdit:focus {background-color: #474e58}' # noqa
|
||||
'QLineEdit:disabled {color: #6a6a6a; background-color: #373737}'
|
||||
)
|
||||
|
||||
|
||||
class FlameTreeWidget(QtWidgets.QTreeWidget):
|
||||
"""
|
||||
Custom Qt Flame Tree Widget
|
||||
|
||||
To use:
|
||||
|
||||
tree_headers = ['Header1', 'Header2', 'Header3', 'Header4']
|
||||
tree = FlameTreeWidget(tree_headers, window)
|
||||
"""
|
||||
|
||||
def __init__(self, tree_headers, parent_window, *args, **kwargs):
|
||||
super(FlameTreeWidget, self).__init__(*args, **kwargs)
|
||||
|
||||
self.setMinimumWidth(1000)
|
||||
self.setMinimumHeight(300)
|
||||
self.setSortingEnabled(True)
|
||||
self.sortByColumn(0, QtCore.Qt.AscendingOrder)
|
||||
self.setAlternatingRowColors(True)
|
||||
self.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.setStyleSheet(
|
||||
'QTreeWidget {color: #9a9a9a; background-color: #2a2a2a; alternate-background-color: #2d2d2d; font: 14px "Discreet"}' # noqa
|
||||
'QTreeWidget::item:selected {color: #d9d9d9; background-color: #474747; border: 1px solid #111111}' # noqa
|
||||
'QHeaderView {color: #9a9a9a; background-color: #393939; font: 14px "Discreet"}' # noqa
|
||||
'QTreeWidget::item:selected {selection-background-color: #111111}'
|
||||
'QMenu {color: #9a9a9a; background-color: #24303d; font: 14px "Discreet"}' # noqa
|
||||
'QMenu::item:selected {color: #d9d9d9; background-color: #3a4551}'
|
||||
)
|
||||
self.verticalScrollBar().setStyleSheet('color: #818181')
|
||||
self.horizontalScrollBar().setStyleSheet('color: #818181')
|
||||
self.setHeaderLabels(tree_headers)
|
||||
|
||||
|
||||
class FlameButton(QtWidgets.QPushButton):
|
||||
"""
|
||||
Custom Qt Flame Button Widget
|
||||
|
||||
To use:
|
||||
|
||||
button = FlameButton('Button Name', do_this_when_pressed, window)
|
||||
"""
|
||||
|
||||
def __init__(self, button_name, do_when_pressed, parent_window,
|
||||
*args, **kwargs):
|
||||
super(FlameButton, self).__init__(*args, **kwargs)
|
||||
|
||||
self.setText(button_name)
|
||||
self.setParent(parent_window)
|
||||
self.setMinimumSize(QtCore.QSize(110, 28))
|
||||
self.setMaximumSize(QtCore.QSize(110, 28))
|
||||
self.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.clicked.connect(do_when_pressed)
|
||||
self.setStyleSheet(
|
||||
'QPushButton {color: #9a9a9a; background-color: #424142; border-top: 1px inset #555555; border-bottom: 1px inset black; font: 14px "Discreet"}' # noqa
|
||||
'QPushButton:pressed {color: #d9d9d9; background-color: #4f4f4f; border-top: 1px inset #666666; font: italic}' # noqa
|
||||
'QPushButton:disabled {color: #747474; background-color: #353535; border-top: 1px solid #444444; border-bottom: 1px solid #242424}' # noqa
|
||||
)
|
||||
|
||||
|
||||
class FlamePushButton(QtWidgets.QPushButton):
|
||||
"""
|
||||
Custom Qt Flame Push Button Widget
|
||||
|
||||
To use:
|
||||
|
||||
pushbutton = FlamePushButton(' Button Name', True_or_False, window)
|
||||
"""
|
||||
|
||||
def __init__(self, button_name, button_checked, parent_window,
|
||||
*args, **kwargs):
|
||||
super(FlamePushButton, self).__init__(*args, **kwargs)
|
||||
|
||||
self.setText(button_name)
|
||||
self.setParent(parent_window)
|
||||
self.setCheckable(True)
|
||||
self.setChecked(button_checked)
|
||||
self.setMinimumSize(155, 28)
|
||||
self.setMaximumSize(155, 28)
|
||||
self.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.setStyleSheet(
|
||||
'QPushButton {color: #9a9a9a; background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop: .93 #424142, stop: .94 #2e3b48); text-align: left; border-top: 1px inset #555555; border-bottom: 1px inset black; font: 14px "Discreet"}' # noqa
|
||||
'QPushButton:checked {color: #d9d9d9; background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop: .93 #4f4f4f, stop: .94 #5a7fb4); font: italic; border: 1px inset black; border-bottom: 1px inset #404040; border-right: 1px inset #404040}' # noqa
|
||||
'QPushButton:disabled {color: #6a6a6a; background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop: .93 #383838, stop: .94 #353535); font: light; border-top: 1px solid #575757; border-bottom: 1px solid #242424; border-right: 1px solid #353535; border-left: 1px solid #353535}' # noqa
|
||||
'QToolTip {color: black; background-color: #ffffde; border: black solid 1px}' # noqa
|
||||
)
|
||||
|
||||
|
||||
class FlamePushButtonMenu(QtWidgets.QPushButton):
|
||||
"""
|
||||
Custom Qt Flame Menu Push Button Widget
|
||||
|
||||
To use:
|
||||
|
||||
push_button_menu_options = ['Item 1', 'Item 2', 'Item 3', 'Item 4']
|
||||
menu_push_button = FlamePushButtonMenu('push_button_name',
|
||||
push_button_menu_options, window)
|
||||
|
||||
or
|
||||
|
||||
push_button_menu_options = ['Item 1', 'Item 2', 'Item 3', 'Item 4']
|
||||
menu_push_button = FlamePushButtonMenu(push_button_menu_options[0],
|
||||
push_button_menu_options, window)
|
||||
"""
|
||||
selection_changed = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, button_name, menu_options, parent_window,
|
||||
*args, **kwargs):
|
||||
super(FlamePushButtonMenu, self).__init__(*args, **kwargs)
|
||||
|
||||
self.setParent(parent_window)
|
||||
self.setMinimumHeight(28)
|
||||
self.setMinimumWidth(110)
|
||||
self.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.setStyleSheet(
|
||||
'QPushButton {color: #9a9a9a; background-color: #24303d; font: 14px "Discreet"}' # noqa
|
||||
'QPushButton:disabled {color: #747474; background-color: #353535; border-top: 1px solid #444444; border-bottom: 1px solid #242424}' # noqa
|
||||
)
|
||||
|
||||
pushbutton_menu = QtWidgets.QMenu(parent_window)
|
||||
pushbutton_menu.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
pushbutton_menu.setStyleSheet(
|
||||
'QMenu {color: #9a9a9a; background-color:#24303d; font: 14px "Discreet"}' # noqa
|
||||
'QMenu::item:selected {color: #d9d9d9; background-color: #3a4551}'
|
||||
)
|
||||
|
||||
self._pushbutton_menu = pushbutton_menu
|
||||
self.setMenu(pushbutton_menu)
|
||||
self.set_menu_options(menu_options, button_name)
|
||||
|
||||
def set_menu_options(self, menu_options, current_option=None):
|
||||
self._pushbutton_menu.clear()
|
||||
current_option = current_option or menu_options[0]
|
||||
|
||||
for option in menu_options:
|
||||
action = self._pushbutton_menu.addAction(option)
|
||||
action.triggered.connect(self._on_action_trigger)
|
||||
|
||||
if current_option is not None:
|
||||
self.setText(current_option)
|
||||
|
||||
def _on_action_trigger(self):
|
||||
action = self.sender()
|
||||
self.setText(action.text())
|
||||
self.selection_changed.emit(action.text())
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(__file__)
|
||||
PACKAGE_DIR = os.path.join(SCRIPT_DIR, "modules")
|
||||
sys.path.append(PACKAGE_DIR)
|
||||
|
||||
|
||||
def flame_panel_executor(selection):
|
||||
if "panel_app" in sys.modules.keys():
|
||||
print("panel_app module is already loaded")
|
||||
del sys.modules["panel_app"]
|
||||
print("panel_app module removed from sys.modules")
|
||||
|
||||
import panel_app
|
||||
panel_app.FlameToFtrackPanel(selection)
|
||||
|
||||
|
||||
def scope_sequence(selection):
|
||||
import flame
|
||||
return any(isinstance(item, flame.PySequence) for item in selection)
|
||||
|
||||
|
||||
def get_media_panel_custom_ui_actions():
|
||||
return [
|
||||
{
|
||||
"name": "OpenPype: Ftrack",
|
||||
"actions": [
|
||||
{
|
||||
"name": "Create Shots",
|
||||
"isVisible": scope_sequence,
|
||||
"execute": flame_panel_executor
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -27,6 +27,7 @@ def _sync_utility_scripts(env=None):
|
|||
|
||||
fsd_paths = [os.path.join(
|
||||
HOST_DIR,
|
||||
"api",
|
||||
"utility_scripts"
|
||||
)]
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import os
|
|||
import json
|
||||
import tempfile
|
||||
import contextlib
|
||||
import socket
|
||||
from openpype.lib import (
|
||||
PreLaunchHook, get_openpype_username)
|
||||
from openpype.hosts import flame as opflame
|
||||
|
|
@ -21,7 +22,7 @@ class FlamePrelaunch(PreLaunchHook):
|
|||
flame_python_exe = "/opt/Autodesk/python/2021/bin/python2.7"
|
||||
|
||||
wtc_script_path = os.path.join(
|
||||
opflame.HOST_DIR, "scripts", "wiretap_com.py")
|
||||
opflame.HOST_DIR, "api", "scripts", "wiretap_com.py")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
|
@ -32,6 +33,7 @@ class FlamePrelaunch(PreLaunchHook):
|
|||
"""Hook entry method."""
|
||||
project_doc = self.data["project_doc"]
|
||||
user_name = get_openpype_username()
|
||||
hostname = socket.gethostname() # not returning wiretap host name
|
||||
|
||||
self.log.debug("Collected user \"{}\"".format(user_name))
|
||||
self.log.info(pformat(project_doc))
|
||||
|
|
@ -53,11 +55,12 @@ class FlamePrelaunch(PreLaunchHook):
|
|||
"FieldDominance": "PROGRESSIVE"
|
||||
}
|
||||
|
||||
|
||||
data_to_script = {
|
||||
# from settings
|
||||
"host_name": "localhost",
|
||||
"volume_name": "stonefs",
|
||||
"group_name": "staff",
|
||||
"host_name": os.getenv("FLAME_WIRETAP_HOSTNAME") or hostname,
|
||||
"volume_name": os.getenv("FLAME_WIRETAP_VOLUME"),
|
||||
"group_name": os.getenv("FLAME_WIRETAP_GROUP"),
|
||||
"color_policy": "ACES 1.1",
|
||||
|
||||
# from project
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import sys
|
||||
|
||||
from avalon.vendor.Qt import QtGui
|
||||
from Qt import QtGui
|
||||
import avalon.fusion
|
||||
from avalon import io
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from avalon import api, style
|
||||
from avalon.vendor.Qt import QtGui, QtWidgets
|
||||
from Qt import QtGui, QtWidgets
|
||||
|
||||
import avalon.fusion
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from avalon.vendor.Qt import QtWidgets
|
||||
from Qt import QtWidgets
|
||||
from avalon.vendor import qtawesome
|
||||
import avalon.fusion as avalon
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ import os
|
|||
import glob
|
||||
import logging
|
||||
|
||||
from Qt import QtWidgets, QtCore
|
||||
|
||||
import avalon.io as io
|
||||
import avalon.api as api
|
||||
import avalon.pipeline as pipeline
|
||||
import avalon.fusion
|
||||
import avalon.style as style
|
||||
from avalon.vendor.Qt import QtWidgets, QtCore
|
||||
from avalon.vendor import qtawesome as qta
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -126,7 +126,8 @@ class CollectFarmRender(openpype.lib.abstract_collect_render.
|
|||
# because of using 'renderFarm' as a family, replace 'Farm' with
|
||||
# capitalized task name - issue of avalon-core Creator app
|
||||
subset_name = node.split("/")[1]
|
||||
task_name = context.data["anatomyData"]["task"].capitalize()
|
||||
task_name = context.data["anatomyData"]["task"][
|
||||
"name"].capitalize()
|
||||
replace_str = ""
|
||||
if task_name.lower() not in subset_name.lower():
|
||||
replace_str = task_name
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class CollectPalettes(pyblish.api.ContextPlugin):
|
|||
|
||||
# skip collecting if not in allowed task
|
||||
if self.allowed_tasks:
|
||||
task_name = context.data["anatomyData"]["task"].lower()
|
||||
task_name = context.data["anatomyData"]["task"]["name"].lower()
|
||||
if (not any([re.search(pattern, task_name)
|
||||
for pattern in self.allowed_tasks])):
|
||||
return
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ def add_implementation_envs(env, _app):
|
|||
# Add requirements to HIERO_PLUGIN_PATH
|
||||
pype_root = os.environ["OPENPYPE_REPOS_ROOT"]
|
||||
new_hiero_paths = [
|
||||
os.path.join(pype_root, "openpype", "hosts", "hiero", "startup")
|
||||
os.path.join(pype_root, "openpype", "hosts", "hiero", "api", "startup")
|
||||
]
|
||||
old_hiero_path = env.get("HIERO_PLUGIN_PATH") or ""
|
||||
for path in old_hiero_path.split(os.pathsep):
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from .pipeline import (
|
|||
|
||||
from .lib import (
|
||||
pype_tag_name,
|
||||
flatten,
|
||||
get_track_items,
|
||||
get_current_project,
|
||||
get_current_sequence,
|
||||
|
|
@ -75,6 +76,7 @@ __all__ = [
|
|||
|
||||
# Lib functions
|
||||
"pype_tag_name",
|
||||
"flatten",
|
||||
"get_track_items",
|
||||
"get_current_project",
|
||||
"get_current_sequence",
|
||||
|
|
|
|||
|
|
@ -4,15 +4,15 @@ Host specific functions where host api is connected
|
|||
import os
|
||||
import re
|
||||
import sys
|
||||
import platform
|
||||
import ast
|
||||
import shutil
|
||||
import hiero
|
||||
from Qt import QtWidgets
|
||||
import avalon.api as avalon
|
||||
import avalon.io
|
||||
from avalon.vendor.Qt import QtWidgets
|
||||
from openpype.api import (Logger, Anatomy, get_anatomy_settings)
|
||||
from . import tags
|
||||
import shutil
|
||||
from compiler.ast import flatten
|
||||
|
||||
try:
|
||||
from PySide.QtCore import QFile, QTextStream
|
||||
|
|
@ -30,6 +30,7 @@ self = sys.modules[__name__]
|
|||
self._has_been_setup = False
|
||||
self._has_menu = False
|
||||
self._registered_gui = None
|
||||
self._parent = None
|
||||
self.pype_tag_name = "openpypeData"
|
||||
self.default_sequence_name = "openpypeSequence"
|
||||
self.default_bin_name = "openpypeBin"
|
||||
|
|
@ -37,6 +38,14 @@ self.default_bin_name = "openpypeBin"
|
|||
AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype")
|
||||
|
||||
|
||||
def flatten(_list):
|
||||
for item in _list:
|
||||
if isinstance(item, (list, tuple)):
|
||||
for sub_item in flatten(item):
|
||||
yield sub_item
|
||||
else:
|
||||
yield item
|
||||
|
||||
def get_current_project(remove_untitled=False):
|
||||
projects = flatten(hiero.core.projects())
|
||||
if not remove_untitled:
|
||||
|
|
@ -249,7 +258,7 @@ def set_track_item_pype_tag(track_item, data=None):
|
|||
Returns:
|
||||
hiero.core.Tag
|
||||
"""
|
||||
data = data or dict()
|
||||
data = data or {}
|
||||
|
||||
# basic Tag's attribute
|
||||
tag_data = {
|
||||
|
|
@ -283,7 +292,7 @@ def get_track_item_pype_data(track_item):
|
|||
Returns:
|
||||
dict: data found on pype tag
|
||||
"""
|
||||
data = dict()
|
||||
data = {}
|
||||
# get pype data tag from track item
|
||||
tag = get_track_item_pype_tag(track_item)
|
||||
|
||||
|
|
@ -298,8 +307,20 @@ def get_track_item_pype_data(track_item):
|
|||
|
||||
try:
|
||||
# capture exceptions which are related to strings only
|
||||
value = ast.literal_eval(v)
|
||||
except (ValueError, SyntaxError):
|
||||
if re.match(r"^[\d]+$", v):
|
||||
value = int(v)
|
||||
elif re.match(r"^True$", v):
|
||||
value = True
|
||||
elif re.match(r"^False$", v):
|
||||
value = False
|
||||
elif re.match(r"^None$", v):
|
||||
value = None
|
||||
elif re.match(r"^[\w\d_]+$", v):
|
||||
value = v
|
||||
else:
|
||||
value = ast.literal_eval(v)
|
||||
except (ValueError, SyntaxError) as msg:
|
||||
log.warning(msg)
|
||||
value = v
|
||||
|
||||
data.update({key: value})
|
||||
|
|
@ -728,9 +749,14 @@ def get_selected_track_items(sequence=None):
|
|||
def set_selected_track_items(track_items_list, sequence=None):
|
||||
_sequence = sequence or get_current_sequence()
|
||||
|
||||
# make sure only trackItems are in list selection
|
||||
only_track_items = [
|
||||
i for i in track_items_list
|
||||
if isinstance(i, hiero.core.TrackItem)]
|
||||
|
||||
# Getting selection
|
||||
timeline_editor = hiero.ui.getTimelineEditor(_sequence)
|
||||
return timeline_editor.setSelection(track_items_list)
|
||||
return timeline_editor.setSelection(only_track_items)
|
||||
|
||||
|
||||
def _read_doc_from_path(path):
|
||||
|
|
@ -758,6 +784,13 @@ def _set_hrox_project_knobs(doc, **knobs):
|
|||
# set attributes to Project Tag
|
||||
proj_elem = doc.documentElement().firstChildElement("Project")
|
||||
for k, v in knobs.items():
|
||||
if "ocioconfigpath" in k:
|
||||
paths_to_format = v[platform.system().lower()]
|
||||
for _path in paths_to_format:
|
||||
v = _path.format(**os.environ)
|
||||
if not os.path.exists(v):
|
||||
continue
|
||||
log.debug("Project colorspace knob `{}` was set to `{}`".format(k, v))
|
||||
if isinstance(v, dict):
|
||||
continue
|
||||
proj_elem.setAttribute(str(k), v)
|
||||
|
|
@ -1029,3 +1062,15 @@ def before_project_save(event):
|
|||
|
||||
# also mark old versions of loaded containers
|
||||
check_inventory_versions()
|
||||
|
||||
|
||||
def get_main_window():
|
||||
"""Acquire Nuke's main window"""
|
||||
if self._parent is None:
|
||||
top_widgets = QtWidgets.QApplication.topLevelWidgets()
|
||||
name = "Foundry::UI::DockMainWindow"
|
||||
main_window = next(widget for widget in top_widgets if
|
||||
widget.inherits("QMainWindow") and
|
||||
widget.metaObject().className() == name)
|
||||
self._parent = main_window
|
||||
return self._parent
|
||||
|
|
|
|||
|
|
@ -37,12 +37,16 @@ def menu_install():
|
|||
Installing menu into Hiero
|
||||
|
||||
"""
|
||||
from Qt import QtGui
|
||||
from . import (
|
||||
publish, launch_workfiles_app, reload_config,
|
||||
apply_colorspace_project, apply_colorspace_clips
|
||||
)
|
||||
from .lib import get_main_window
|
||||
|
||||
main_window = get_main_window()
|
||||
|
||||
# here is the best place to add menu
|
||||
from avalon.vendor.Qt import QtGui
|
||||
|
||||
menu_name = os.environ['AVALON_LABEL']
|
||||
|
||||
|
|
@ -86,18 +90,24 @@ def menu_install():
|
|||
|
||||
creator_action = menu.addAction("Create ...")
|
||||
creator_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png"))
|
||||
creator_action.triggered.connect(host_tools.show_creator)
|
||||
creator_action.triggered.connect(
|
||||
lambda: host_tools.show_creator(parent=main_window)
|
||||
)
|
||||
|
||||
loader_action = menu.addAction("Load ...")
|
||||
loader_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png"))
|
||||
loader_action.triggered.connect(host_tools.show_loader)
|
||||
loader_action.triggered.connect(
|
||||
lambda: host_tools.show_loader(parent=main_window)
|
||||
)
|
||||
|
||||
sceneinventory_action = menu.addAction("Manage ...")
|
||||
sceneinventory_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png"))
|
||||
sceneinventory_action.triggered.connect(host_tools.show_scene_inventory)
|
||||
menu.addSeparator()
|
||||
sceneinventory_action.triggered.connect(
|
||||
lambda: host_tools.show_scene_inventory(parent=main_window)
|
||||
)
|
||||
|
||||
if os.getenv("OPENPYPE_DEVELOP"):
|
||||
menu.addSeparator()
|
||||
reload_action = menu.addAction("Reload pipeline")
|
||||
reload_action.setIcon(QtGui.QIcon("icons:ColorAdd.png"))
|
||||
reload_action.triggered.connect(reload_config)
|
||||
|
|
@ -110,3 +120,10 @@ def menu_install():
|
|||
apply_colorspace_c_action = menu.addAction("Apply Colorspace Clips")
|
||||
apply_colorspace_c_action.setIcon(QtGui.QIcon("icons:ColorAdd.png"))
|
||||
apply_colorspace_c_action.triggered.connect(apply_colorspace_clips)
|
||||
|
||||
menu.addSeparator()
|
||||
|
||||
exeprimental_action = menu.addAction("Experimental tools...")
|
||||
exeprimental_action.triggered.connect(
|
||||
lambda: host_tools.show_experimental_tools_dialog(parent=main_window)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import os
|
|||
import re
|
||||
import sys
|
||||
import ast
|
||||
from compiler.ast import flatten
|
||||
import opentimelineio as otio
|
||||
from . import utils
|
||||
import hiero.core
|
||||
|
|
@ -29,6 +28,15 @@ self.timeline = None
|
|||
self.include_tags = True
|
||||
|
||||
|
||||
def flatten(_list):
|
||||
for item in _list:
|
||||
if isinstance(item, (list, tuple)):
|
||||
for sub_item in flatten(item):
|
||||
yield sub_item
|
||||
else:
|
||||
yield item
|
||||
|
||||
|
||||
def get_current_hiero_project(remove_untitled=False):
|
||||
projects = flatten(hiero.core.projects())
|
||||
if not remove_untitled:
|
||||
|
|
@ -74,13 +82,11 @@ def create_time_effects(otio_clip, track_item):
|
|||
otio_effect = otio.schema.LinearTimeWarp()
|
||||
otio_effect.name = "Speed"
|
||||
otio_effect.time_scalar = speed
|
||||
otio_effect.metadata = {}
|
||||
|
||||
# freeze frame effect
|
||||
if speed == 0.:
|
||||
otio_effect = otio.schema.FreezeFrame()
|
||||
otio_effect.name = "FreezeFrame"
|
||||
otio_effect.metadata = {}
|
||||
|
||||
if otio_effect:
|
||||
# add otio effect to clip effects
|
||||
|
|
@ -209,9 +209,11 @@ def update_container(track_item, data=None):
|
|||
|
||||
def launch_workfiles_app(*args):
|
||||
''' Wrapping function for workfiles launcher '''
|
||||
from .lib import get_main_window
|
||||
|
||||
main_window = get_main_window()
|
||||
# show workfile gui
|
||||
host_tools.show_workfiles()
|
||||
host_tools.show_workfiles(parent=main_window)
|
||||
|
||||
|
||||
def publish(parent):
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from avalon.vendor import qargparse
|
|||
import avalon.api as avalon
|
||||
import openpype.api as openpype
|
||||
from . import lib
|
||||
from copy import deepcopy
|
||||
|
||||
log = openpype.Logger().get_logger(__name__)
|
||||
|
||||
|
|
@ -190,7 +191,7 @@ class CreatorWidget(QtWidgets.QDialog):
|
|||
|
||||
content_layout = content_layout or self.content_layout[-1]
|
||||
# fix order of process by defined order value
|
||||
ordered_keys = data.keys()
|
||||
ordered_keys = list(data.keys())
|
||||
for k, v in data.items():
|
||||
try:
|
||||
# try removing a key from index which should
|
||||
|
|
@ -799,7 +800,8 @@ class PublishClip:
|
|||
# increasing steps by index of rename iteration
|
||||
self.count_steps *= self.rename_index
|
||||
|
||||
hierarchy_formating_data = dict()
|
||||
hierarchy_formating_data = {}
|
||||
hierarchy_data = deepcopy(self.hierarchy_data)
|
||||
_data = self.track_item_default_data.copy()
|
||||
if self.ui_inputs:
|
||||
# adding tag metadata from ui
|
||||
|
|
@ -824,19 +826,19 @@ class PublishClip:
|
|||
_data.update({"shot": self.shot_num})
|
||||
|
||||
# solve # in test to pythonic expression
|
||||
for _k, _v in self.hierarchy_data.items():
|
||||
for _k, _v in hierarchy_data.items():
|
||||
if "#" not in _v["value"]:
|
||||
continue
|
||||
self.hierarchy_data[
|
||||
hierarchy_data[
|
||||
_k]["value"] = self._replace_hash_to_expression(
|
||||
_k, _v["value"])
|
||||
|
||||
# fill up pythonic expresisons in hierarchy data
|
||||
for k, _v in self.hierarchy_data.items():
|
||||
for k, _v in hierarchy_data.items():
|
||||
hierarchy_formating_data[k] = _v["value"].format(**_data)
|
||||
else:
|
||||
# if no gui mode then just pass default data
|
||||
hierarchy_formating_data = self.hierarchy_data
|
||||
hierarchy_formating_data = hierarchy_data
|
||||
|
||||
tag_hierarchy_data = self._solve_tag_hierarchy_data(
|
||||
hierarchy_formating_data
|
||||
|
|
@ -886,30 +888,38 @@ class PublishClip:
|
|||
"families": [self.data["family"]]
|
||||
}
|
||||
|
||||
def _convert_to_entity(self, key):
|
||||
def _convert_to_entity(self, type, template):
|
||||
""" Converting input key to key with type. """
|
||||
# convert to entity type
|
||||
entity_type = self.types.get(key, None)
|
||||
entity_type = self.types.get(type, None)
|
||||
|
||||
assert entity_type, "Missing entity type for `{}`".format(
|
||||
key
|
||||
type
|
||||
)
|
||||
|
||||
# first collect formating data to use for formating template
|
||||
formating_data = {}
|
||||
for _k, _v in self.hierarchy_data.items():
|
||||
value = _v["value"].format(
|
||||
**self.track_item_default_data)
|
||||
formating_data[_k] = value
|
||||
|
||||
return {
|
||||
"entity_type": entity_type,
|
||||
"entity_name": self.hierarchy_data[key]["value"].format(
|
||||
**self.track_item_default_data
|
||||
"entity_name": template.format(
|
||||
**formating_data
|
||||
)
|
||||
}
|
||||
|
||||
def _create_parents(self):
|
||||
""" Create parents and return it in list. """
|
||||
self.parents = list()
|
||||
self.parents = []
|
||||
|
||||
patern = re.compile(self.parents_search_patern)
|
||||
par_split = [patern.findall(t).pop()
|
||||
|
||||
par_split = [(patern.findall(t).pop(), t)
|
||||
for t in self.hierarchy.split("/")]
|
||||
|
||||
for key in par_split:
|
||||
parent = self._convert_to_entity(key)
|
||||
for type, template in par_split:
|
||||
parent = self._convert_to_entity(type, template)
|
||||
self.parents.append(parent)
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
|
|
@ -18,7 +18,7 @@ except:
|
|||
### Magic Widget Finding Methods - This stuff crawls all the PySide widgets, looking for an answer
|
||||
def findWidget(w):
|
||||
global foundryWidgets
|
||||
if 'Foundry' in w.metaObject().className():
|
||||
if "Foundry" in w.metaObject().className():
|
||||
foundryWidgets += [w]
|
||||
|
||||
for c in w.children():
|
||||
|
|
@ -49,7 +49,7 @@ def activeSpreadsheetTreeView():
|
|||
Does some PySide widget Magic to detect the Active Spreadsheet TreeView.
|
||||
"""
|
||||
spreadsheetViews = getFoundryWidgetsWithClassName(
|
||||
filter='SpreadsheetTreeView')
|
||||
filter="SpreadsheetTreeView")
|
||||
for spreadSheet in spreadsheetViews:
|
||||
if spreadSheet.hasFocus():
|
||||
activeSpreadSheet = spreadSheet
|
||||
|
|
@ -77,23 +77,23 @@ class SpreadsheetExportCSVAction(QAction):
|
|||
spreadsheetTreeView = activeSpreadsheetTreeView()
|
||||
|
||||
if not spreadsheetTreeView:
|
||||
return 'Unable to detect the active TreeView.'
|
||||
return "Unable to detect the active TreeView."
|
||||
seq = hiero.ui.activeView().sequence()
|
||||
if not seq:
|
||||
print 'Unable to detect the active Sequence from the activeView.'
|
||||
print("Unable to detect the active Sequence from the activeView.")
|
||||
return
|
||||
|
||||
# The data model of the QTreeView
|
||||
model = spreadsheetTreeView.model()
|
||||
|
||||
csvSavePath = os.path.join(QDir.homePath(), 'Desktop',
|
||||
seq.name() + '.csv')
|
||||
csvSavePath = os.path.join(QDir.homePath(), "Desktop",
|
||||
seq.name() + ".csv")
|
||||
savePath, filter = QFileDialog.getSaveFileName(
|
||||
None,
|
||||
caption="Export Spreadsheet to .CSV as...",
|
||||
dir=csvSavePath,
|
||||
filter="*.csv")
|
||||
print 'Saving To: ' + str(savePath)
|
||||
print("Saving To: {}".format(savePath))
|
||||
|
||||
# Saving was cancelled...
|
||||
if len(savePath) == 0:
|
||||
|
|
@ -101,12 +101,12 @@ class SpreadsheetExportCSVAction(QAction):
|
|||
|
||||
# Get the Visible Header Columns from the QTreeView
|
||||
|
||||
#csvHeader = ['Event', 'Status', 'Shot Name', 'Reel', 'Track', 'Speed', 'Src In', 'Src Out','Src Duration', 'Dst In', 'Dst Out', 'Dst Duration', 'Clip', 'Clip Media']
|
||||
#csvHeader = ["Event", "Status", "Shot Name", "Reel", "Track", "Speed", "Src In", "Src Out","Src Duration", "Dst In", "Dst Out", "Dst Duration", "Clip", "Clip Media"]
|
||||
|
||||
# Get a CSV writer object
|
||||
f = open(savePath, 'w')
|
||||
f = open(savePath, "w")
|
||||
csvWriter = csv.writer(
|
||||
f, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)
|
||||
f, delimiter=',', quotechar="|", quoting=csv.QUOTE_MINIMAL)
|
||||
|
||||
# This is a list of the Column titles
|
||||
csvHeader = []
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__author__ = "Daniel Flehner Heen"
|
||||
|
|
@ -9,7 +8,7 @@ import hiero.core
|
|||
from hiero.core import util
|
||||
|
||||
import opentimelineio as otio
|
||||
from openpype.hosts.hiero.otio import hiero_export
|
||||
from openpype.hosts.hiero.api.otio import hiero_export
|
||||
|
||||
class OTIOExportTask(hiero.core.TaskBase):
|
||||
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__author__ = "Daniel Flehner Heen"
|
||||
__credits__ = ["Jakub Jezek", "Daniel Flehner Heen"]
|
||||
|
||||
import hiero.ui
|
||||
import OTIOExportTask
|
||||
from .OTIOExportTask import (
|
||||
OTIOExportTask,
|
||||
OTIOExportPreset
|
||||
)
|
||||
|
||||
try:
|
||||
# Hiero >= 11.x
|
||||
|
|
@ -20,14 +22,14 @@ except ImportError:
|
|||
|
||||
FormLayout = QFormLayout # lint:ok
|
||||
|
||||
from openpype.hosts.hiero.otio import hiero_export
|
||||
from openpype.hosts.hiero.api.otio import hiero_export
|
||||
|
||||
class OTIOExportUI(hiero.ui.TaskUIBase):
|
||||
def __init__(self, preset):
|
||||
"""Initialize"""
|
||||
hiero.ui.TaskUIBase.__init__(
|
||||
self,
|
||||
OTIOExportTask.OTIOExportTask,
|
||||
OTIOExportTask,
|
||||
preset,
|
||||
"OTIO Exporter"
|
||||
)
|
||||
|
|
@ -67,6 +69,6 @@ class OTIOExportUI(hiero.ui.TaskUIBase):
|
|||
|
||||
|
||||
hiero.ui.taskUIRegistry.registerTaskUI(
|
||||
OTIOExportTask.OTIOExportPreset,
|
||||
OTIOExportPreset,
|
||||
OTIOExportUI
|
||||
)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
from .OTIOExportTask import OTIOExportTask
|
||||
from .OTIOExportUI import OTIOExportUI
|
||||
|
||||
__all__ = [
|
||||
"OTIOExportTask",
|
||||
"OTIOExportUI"
|
||||
]
|
||||
|
|
@ -21,7 +21,7 @@ def __trackActiveProjectHandler(event):
|
|||
global gTrackedActiveProject
|
||||
selection = event.sender.selection()
|
||||
binSelection = selection
|
||||
if len(binSelection) > 0 and hasattr(binSelection[0], 'project'):
|
||||
if len(binSelection) > 0 and hasattr(binSelection[0], "project"):
|
||||
proj = binSelection[0].project()
|
||||
|
||||
# We only store this if its a valid, active User Project
|
||||
|
|
@ -30,18 +30,18 @@ def __trackActiveProjectHandler(event):
|
|||
|
||||
|
||||
hiero.core.events.registerInterest(
|
||||
'kSelectionChanged/kBin', __trackActiveProjectHandler)
|
||||
"kSelectionChanged/kBin", __trackActiveProjectHandler)
|
||||
hiero.core.events.registerInterest(
|
||||
'kSelectionChanged/kTimeline', __trackActiveProjectHandler)
|
||||
"kSelectionChanged/kTimeline", __trackActiveProjectHandler)
|
||||
hiero.core.events.registerInterest(
|
||||
'kSelectionChanged/Spreadsheet', __trackActiveProjectHandler)
|
||||
"kSelectionChanged/Spreadsheet", __trackActiveProjectHandler)
|
||||
|
||||
|
||||
def activeProject():
|
||||
"""hiero.ui.activeProject() -> returns the current Project
|
||||
|
||||
Note: There is not technically a notion of a 'active' Project in Hiero/NukeStudio, as it is a multi-project App.
|
||||
This method determines what is 'active' by going down the following rules...
|
||||
Note: There is not technically a notion of a "active" Project in Hiero/NukeStudio, as it is a multi-project App.
|
||||
This method determines what is "active" by going down the following rules...
|
||||
|
||||
# 1 - If the current Viewer (hiero.ui.currentViewer) contains a Clip or Sequence, this item is assumed to give the active Project
|
||||
# 2 - If nothing is currently in the Viewer, look to the active View, determine project from active selection
|
||||
|
|
@ -54,7 +54,7 @@ def activeProject():
|
|||
|
||||
# Case 1 : Look for what the current Viewr tells us - this might not be what we want, and relies on hiero.ui.currentViewer() being robust.
|
||||
cv = hiero.ui.currentViewer().player().sequence()
|
||||
if hasattr(cv, 'project'):
|
||||
if hasattr(cv, "project"):
|
||||
activeProject = cv.project()
|
||||
else:
|
||||
# Case 2: We can't determine a project from the current Viewer, so try seeing what's selected in the activeView
|
||||
|
|
@ -66,16 +66,16 @@ def activeProject():
|
|||
|
||||
# Handle the case where nothing is selected in the active view
|
||||
if len(selection) == 0:
|
||||
# It's possible that there is no selection in a Timeline/Spreadsheet, but these views have 'sequence' method, so try that...
|
||||
# It's possible that there is no selection in a Timeline/Spreadsheet, but these views have "sequence" method, so try that...
|
||||
if isinstance(hiero.ui.activeView(), (hiero.ui.TimelineEditor, hiero.ui.SpreadsheetView)):
|
||||
activeSequence = activeView.sequence()
|
||||
if hasattr(currentItem, 'project'):
|
||||
if hasattr(currentItem, "project"):
|
||||
activeProject = activeSequence.project()
|
||||
|
||||
# The active view has a selection... assume that the first item in the selection has the active Project
|
||||
else:
|
||||
currentItem = selection[0]
|
||||
if hasattr(currentItem, 'project'):
|
||||
if hasattr(currentItem, "project"):
|
||||
activeProject = currentItem.project()
|
||||
|
||||
# Finally, Cases 3 and 4...
|
||||
|
|
@ -156,9 +156,14 @@ class SaveAllProjects(QAction):
|
|||
for proj in allProjects:
|
||||
try:
|
||||
proj.save()
|
||||
print 'Saved Project: %s to: %s ' % (proj.name(), proj.path())
|
||||
print("Saved Project: {} to: {} ".format(
|
||||
proj.name(), proj.path()
|
||||
))
|
||||
except:
|
||||
print 'Unable to save Project: %s to: %s. Check file permissions.' % (proj.name(), proj.path())
|
||||
print((
|
||||
"Unable to save Project: {} to: {}. "
|
||||
"Check file permissions.").format(
|
||||
proj.name(), proj.path()))
|
||||
|
||||
def eventHandler(self, event):
|
||||
event.menu.addAction(self)
|
||||
|
|
@ -190,32 +195,38 @@ class SaveNewProjectVersion(QAction):
|
|||
v = None
|
||||
prefix = None
|
||||
try:
|
||||
(prefix, v) = version_get(path, 'v')
|
||||
except ValueError, msg:
|
||||
print msg
|
||||
(prefix, v) = version_get(path, "v")
|
||||
except ValueError as msg:
|
||||
print(msg)
|
||||
|
||||
if (prefix is not None) and (v is not None):
|
||||
v = int(v)
|
||||
newPath = version_set(path, prefix, v, v + 1)
|
||||
try:
|
||||
proj.saveAs(newPath)
|
||||
print 'Saved new project version: %s to: %s ' % (oldName, newPath)
|
||||
print("Saved new project version: {} to: {} ".format(
|
||||
oldName, newPath))
|
||||
except:
|
||||
print 'Unable to save Project: %s. Check file permissions.' % (oldName)
|
||||
print((
|
||||
"Unable to save Project: {}. Check file permissions."
|
||||
).format(oldName))
|
||||
else:
|
||||
newPath = path.replace(".hrox", "_v01.hrox")
|
||||
answer = nuke.ask(
|
||||
'%s does not contain a version number.\nDo you want to save as %s?' % (proj, newPath))
|
||||
"%s does not contain a version number.\nDo you want to save as %s?" % (proj, newPath))
|
||||
if answer:
|
||||
try:
|
||||
proj.saveAs(newPath)
|
||||
print 'Saved new project version: %s to: %s ' % (oldName, newPath)
|
||||
print("Saved new project version: {} to: {} ".format(
|
||||
oldName, newPath))
|
||||
except:
|
||||
print 'Unable to save Project: %s. Check file permissions.' % (oldName)
|
||||
print((
|
||||
"Unable to save Project: {}. Check file "
|
||||
"permissions.").format(oldName))
|
||||
|
||||
def eventHandler(self, event):
|
||||
self.selectedProjects = []
|
||||
if hasattr(event.sender, 'selection') and event.sender.selection() is not None and len(event.sender.selection()) != 0:
|
||||
if hasattr(event.sender, "selection") and event.sender.selection() is not None and len(event.sender.selection()) != 0:
|
||||
selection = event.sender.selection()
|
||||
self.selectedProjects = uniquify(
|
||||
[item.project() for item in selection])
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
"""Puts the selection project into "hiero.selection"""
|
||||
|
||||
import hiero
|
||||
|
||||
|
||||
def selectionChanged(event):
|
||||
hiero.selection = event.sender.selection()
|
||||
|
||||
hiero.core.events.registerInterest("kSelectionChanged", selectionChanged)
|
||||
|
|
@ -23,7 +23,7 @@ class SetFrameRateDialog(QDialog):
|
|||
self._itemSelection = itemSelection
|
||||
|
||||
self._frameRateField = QLineEdit()
|
||||
self._frameRateField.setToolTip('Enter custom frame rate here.')
|
||||
self._frameRateField.setToolTip("Enter custom frame rate here.")
|
||||
self._frameRateField.setValidator(QDoubleValidator(1, 99, 3, self))
|
||||
self._frameRateField.textChanged.connect(self._textChanged)
|
||||
layout.addRow("Enter fps: ",self._frameRateField)
|
||||
|
|
@ -35,13 +35,13 @@ class SetFrameRateDialog(QDialog):
|
|||
self._buttonbox.button(QDialogButtonBox.Ok).setEnabled(False)
|
||||
layout.addRow("",self._buttonbox)
|
||||
self.setLayout(layout)
|
||||
|
||||
|
||||
def _updateOkButtonState(self):
|
||||
# Cancel is always an option but only enable Ok if there is some text.
|
||||
currentFramerate = float(self.currentFramerateString())
|
||||
enableOk = False
|
||||
enableOk = ((currentFramerate > 0.0) and (currentFramerate <= 250.0))
|
||||
print 'enabledOk',enableOk
|
||||
print("enabledOk", enableOk)
|
||||
self._buttonbox.button(QDialogButtonBox.Ok).setEnabled(enableOk)
|
||||
|
||||
def _textChanged(self, newText):
|
||||
|
|
@ -50,32 +50,32 @@ class SetFrameRateDialog(QDialog):
|
|||
# Returns the current frame rate as a string
|
||||
def currentFramerateString(self):
|
||||
return str(self._frameRateField.text())
|
||||
|
||||
|
||||
# Presents the Dialog and sets the Frame rate from a selection
|
||||
def showDialogAndSetFrameRateFromSelection(self):
|
||||
|
||||
|
||||
if self._itemSelection is not None:
|
||||
if self.exec_():
|
||||
# For the Undo loop...
|
||||
|
||||
|
||||
# Construct an TimeBase object for setting the Frame Rate (fps)
|
||||
fps = hiero.core.TimeBase().fromString(self.currentFramerateString())
|
||||
|
||||
|
||||
|
||||
# Set the frame rate for the selected BinItmes
|
||||
for item in self._itemSelection:
|
||||
item.setFramerate(fps)
|
||||
item.setFramerate(fps)
|
||||
return
|
||||
|
||||
# This is just a convenience method for returning QActions with a title, triggered method and icon.
|
||||
def makeAction(title, method, icon = None):
|
||||
action = QAction(title,None)
|
||||
action.setIcon(QIcon(icon))
|
||||
|
||||
|
||||
# We do this magic, so that the title string from the action is used to set the frame rate!
|
||||
def methodWrapper():
|
||||
method(title)
|
||||
|
||||
|
||||
action.triggered.connect( methodWrapper )
|
||||
return action
|
||||
|
||||
|
|
@ -88,13 +88,13 @@ class SetFrameRateMenu:
|
|||
|
||||
|
||||
# ant: Could use hiero.core.defaultFrameRates() here but messes up with string matching because we seem to mix decimal points
|
||||
self.frameRates = ['8','12','12.50','15','23.98','24','25','29.97','30','48','50','59.94','60']
|
||||
self.frameRates = ["8","12","12.50","15","23.98","24","25","29.97","30","48","50","59.94","60"]
|
||||
hiero.core.events.registerInterest("kShowContextMenu/kBin", self.binViewEventHandler)
|
||||
|
||||
|
||||
self.menuActions = []
|
||||
|
||||
def createFrameRateMenus(self,selection):
|
||||
selectedClipFPS = [str(bi.activeItem().framerate()) for bi in selection if (isinstance(bi,hiero.core.BinItem) and hasattr(bi,'activeItem'))]
|
||||
selectedClipFPS = [str(bi.activeItem().framerate()) for bi in selection if (isinstance(bi,hiero.core.BinItem) and hasattr(bi,"activeItem"))]
|
||||
selectedClipFPS = hiero.core.util.uniquify(selectedClipFPS)
|
||||
sameFrameRate = len(selectedClipFPS)==1
|
||||
self.menuActions = []
|
||||
|
|
@ -106,39 +106,41 @@ class SetFrameRateMenu:
|
|||
self.menuActions+=[makeAction(fps,self.setFrameRateFromMenuSelection, icon="icons:remove active.png")]
|
||||
else:
|
||||
self.menuActions+=[makeAction(fps,self.setFrameRateFromMenuSelection, icon=None)]
|
||||
|
||||
|
||||
# Now add Custom... menu
|
||||
self.menuActions+=[makeAction('Custom...',self.setFrameRateFromMenuSelection, icon=None)]
|
||||
|
||||
self.menuActions += [makeAction(
|
||||
"Custom...", self.setFrameRateFromMenuSelection, icon=None)
|
||||
]
|
||||
|
||||
frameRateMenu = QMenu("Set Frame Rate")
|
||||
for a in self.menuActions:
|
||||
frameRateMenu.addAction(a)
|
||||
|
||||
|
||||
return frameRateMenu
|
||||
|
||||
def setFrameRateFromMenuSelection(self, menuSelectionFPS):
|
||||
|
||||
selectedBinItems = [bi.activeItem() for bi in self._selection if (isinstance(bi,hiero.core.BinItem) and hasattr(bi,'activeItem'))]
|
||||
|
||||
selectedBinItems = [bi.activeItem() for bi in self._selection if (isinstance(bi,hiero.core.BinItem) and hasattr(bi,"activeItem"))]
|
||||
currentProject = selectedBinItems[0].project()
|
||||
|
||||
|
||||
with currentProject.beginUndo("Set Frame Rate"):
|
||||
if menuSelectionFPS == 'Custom...':
|
||||
if menuSelectionFPS == "Custom...":
|
||||
self._frameRatesDialog = SetFrameRateDialog(itemSelection = selectedBinItems )
|
||||
self._frameRatesDialog.showDialogAndSetFrameRateFromSelection()
|
||||
|
||||
else:
|
||||
for b in selectedBinItems:
|
||||
b.setFramerate(hiero.core.TimeBase().fromString(menuSelectionFPS))
|
||||
|
||||
|
||||
return
|
||||
|
||||
# This handles events from the Project Bin View
|
||||
def binViewEventHandler(self,event):
|
||||
if not hasattr(event.sender, 'selection'):
|
||||
if not hasattr(event.sender, "selection"):
|
||||
# Something has gone wrong, we should only be here if raised
|
||||
# by the Bin view which gives a selection.
|
||||
return
|
||||
|
||||
|
||||
# Reset the selection to None...
|
||||
self._selection = None
|
||||
s = event.sender.selection()
|
||||
|
|
@ -151,9 +153,9 @@ class SetFrameRateMenu:
|
|||
if len(self._selection)==0:
|
||||
return
|
||||
# Creating the menu based on items selected, to highlight which frame rates are contained
|
||||
|
||||
|
||||
self._frameRateMenu = self.createFrameRateMenus(self._selection)
|
||||
|
||||
|
||||
# Insert the Set Frame Rate Button before the Set Media Colour Transform Action
|
||||
for action in event.menu.actions():
|
||||
if str(action.text()) == "Set Media Colour Transform":
|
||||
|
|
@ -16,29 +16,29 @@ except:
|
|||
from PySide2.QtCore import *
|
||||
|
||||
|
||||
def whereAmI(self, searchType='TrackItem'):
|
||||
def whereAmI(self, searchType="TrackItem"):
|
||||
"""returns a list of TrackItem or Sequnece objects in the Project which contain this Clip.
|
||||
By default this will return a list of TrackItems where the Clip is used in its project.
|
||||
You can also return a list of Sequences by specifying the searchType to be 'Sequence'.
|
||||
You can also return a list of Sequences by specifying the searchType to be "Sequence".
|
||||
Should consider putting this into hiero.core.Clip by default?
|
||||
|
||||
Example usage:
|
||||
|
||||
shotsForClip = clip.whereAmI('TrackItem')
|
||||
sequencesForClip = clip.whereAmI('Sequence')
|
||||
shotsForClip = clip.whereAmI("TrackItem")
|
||||
sequencesForClip = clip.whereAmI("Sequence")
|
||||
"""
|
||||
proj = self.project()
|
||||
|
||||
if ('TrackItem' not in searchType) and ('Sequence' not in searchType):
|
||||
print "searchType argument must be 'TrackItem' or 'Sequence'"
|
||||
if ("TrackItem" not in searchType) and ("Sequence" not in searchType):
|
||||
print("searchType argument must be \"TrackItem\" or \"Sequence\"")
|
||||
return None
|
||||
|
||||
# If user specifies a TrackItem, then it will return
|
||||
searches = hiero.core.findItemsInProject(proj, searchType)
|
||||
|
||||
if len(searches) == 0:
|
||||
print 'Unable to find %s in any items of type: %s' % (str(self),
|
||||
str(searchType))
|
||||
print("Unable to find {} in any items of type: {}".format(
|
||||
str(self), searchType))
|
||||
return None
|
||||
|
||||
# Case 1: Looking for Shots (trackItems)
|
||||
|
|
@ -110,7 +110,7 @@ class VersionAllMenu(object):
|
|||
for shot in sequenceShotManifest[seq]:
|
||||
updateReportString += ' %s\n (New Version: %s)\n' % (
|
||||
shot.name(), shot.currentVersion().name())
|
||||
updateReportString += '\n'
|
||||
updateReportString += "\n"
|
||||
|
||||
infoBox = QMessageBox(hiero.ui.mainWindow())
|
||||
infoBox.setIcon(QMessageBox.Information)
|
||||
|
|
@ -202,7 +202,7 @@ class VersionAllMenu(object):
|
|||
if len(bins) > 0:
|
||||
# Grab the Clips inside of a Bin and append them to a list
|
||||
for bin in bins:
|
||||
clips = hiero.core.findItemsInBin(bin, 'Clip')
|
||||
clips = hiero.core.findItemsInBin(bin, "Clip")
|
||||
for clip in clips:
|
||||
if clip not in clipItems:
|
||||
clipItems.append(clip)
|
||||
|
|
@ -291,7 +291,7 @@ class VersionAllMenu(object):
|
|||
for clip in clipSelection:
|
||||
|
||||
# Look to see if it exists in a TrackItem somewhere...
|
||||
shotUsage = clip.whereAmI('TrackItem')
|
||||
shotUsage = clip.whereAmI("TrackItem")
|
||||
|
||||
# Next, depending on the versionOption, make the appropriate update
|
||||
# There's probably a more neat/compact way of doing this...
|
||||
|
|
@ -326,7 +326,7 @@ class VersionAllMenu(object):
|
|||
# This handles events from the Project Bin View
|
||||
def binViewEventHandler(self, event):
|
||||
|
||||
if not hasattr(event.sender, 'selection'):
|
||||
if not hasattr(event.sender, "selection"):
|
||||
# Something has gone wrong, we should only be here if raised
|
||||
# by the Bin view which gives a selection.
|
||||
return
|
||||
|
|
@ -15,55 +15,55 @@ except:
|
|||
from PySide2.QtWidgets import *
|
||||
from PySide2.QtCore import *
|
||||
|
||||
# Set to True, if you wat 'Set Status' right-click menu, False if not
|
||||
# Set to True, if you wat "Set Status" right-click menu, False if not
|
||||
kAddStatusMenu = True
|
||||
|
||||
# Set to True, if you wat 'Assign Artist' right-click menu, False if not
|
||||
# Set to True, if you wat "Assign Artist" right-click menu, False if not
|
||||
kAssignArtistMenu = True
|
||||
|
||||
# Global list of Artist Name Dictionaries
|
||||
# Note: Override this to add different names, icons, department, IDs.
|
||||
gArtistList = [{
|
||||
'artistName': 'John Smith',
|
||||
'artistIcon': 'icons:TagActor.png',
|
||||
'artistDepartment': '3D',
|
||||
'artistID': 0
|
||||
"artistName": "John Smith",
|
||||
"artistIcon": "icons:TagActor.png",
|
||||
"artistDepartment": "3D",
|
||||
"artistID": 0
|
||||
}, {
|
||||
'artistName': 'Savlvador Dali',
|
||||
'artistIcon': 'icons:TagActor.png',
|
||||
'artistDepartment': 'Roto',
|
||||
'artistID': 1
|
||||
"artistName": "Savlvador Dali",
|
||||
"artistIcon": "icons:TagActor.png",
|
||||
"artistDepartment": "Roto",
|
||||
"artistID": 1
|
||||
}, {
|
||||
'artistName': 'Leonardo Da Vinci',
|
||||
'artistIcon': 'icons:TagActor.png',
|
||||
'artistDepartment': 'Paint',
|
||||
'artistID': 2
|
||||
"artistName": "Leonardo Da Vinci",
|
||||
"artistIcon": "icons:TagActor.png",
|
||||
"artistDepartment": "Paint",
|
||||
"artistID": 2
|
||||
}, {
|
||||
'artistName': 'Claude Monet',
|
||||
'artistIcon': 'icons:TagActor.png',
|
||||
'artistDepartment': 'Comp',
|
||||
'artistID': 3
|
||||
"artistName": "Claude Monet",
|
||||
"artistIcon": "icons:TagActor.png",
|
||||
"artistDepartment": "Comp",
|
||||
"artistID": 3
|
||||
}, {
|
||||
'artistName': 'Pablo Picasso',
|
||||
'artistIcon': 'icons:TagActor.png',
|
||||
'artistDepartment': 'Animation',
|
||||
'artistID': 4
|
||||
"artistName": "Pablo Picasso",
|
||||
"artistIcon": "icons:TagActor.png",
|
||||
"artistDepartment": "Animation",
|
||||
"artistID": 4
|
||||
}]
|
||||
|
||||
# Global Dictionary of Status Tags.
|
||||
# Note: This can be overwritten if you want to add a new status cellType or custom icon
|
||||
# Override the gStatusTags dictionary by adding your own 'Status':'Icon.png' key-value pairs.
|
||||
# Add new custom keys like so: gStatusTags['For Client'] = 'forClient.png'
|
||||
# Override the gStatusTags dictionary by adding your own "Status":"Icon.png" key-value pairs.
|
||||
# Add new custom keys like so: gStatusTags["For Client"] = "forClient.png"
|
||||
gStatusTags = {
|
||||
'Approved': 'icons:status/TagApproved.png',
|
||||
'Unapproved': 'icons:status/TagUnapproved.png',
|
||||
'Ready To Start': 'icons:status/TagReadyToStart.png',
|
||||
'Blocked': 'icons:status/TagBlocked.png',
|
||||
'On Hold': 'icons:status/TagOnHold.png',
|
||||
'In Progress': 'icons:status/TagInProgress.png',
|
||||
'Awaiting Approval': 'icons:status/TagAwaitingApproval.png',
|
||||
'Omitted': 'icons:status/TagOmitted.png',
|
||||
'Final': 'icons:status/TagFinal.png'
|
||||
"Approved": "icons:status/TagApproved.png",
|
||||
"Unapproved": "icons:status/TagUnapproved.png",
|
||||
"Ready To Start": "icons:status/TagReadyToStart.png",
|
||||
"Blocked": "icons:status/TagBlocked.png",
|
||||
"On Hold": "icons:status/TagOnHold.png",
|
||||
"In Progress": "icons:status/TagInProgress.png",
|
||||
"Awaiting Approval": "icons:status/TagAwaitingApproval.png",
|
||||
"Omitted": "icons:status/TagOmitted.png",
|
||||
"Final": "icons:status/TagFinal.png"
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -78,17 +78,17 @@ class CustomSpreadsheetColumns(QObject):
|
|||
|
||||
# Ideally, we'd set this list on a Per Item basis, but this is expensive for a large mixed selection
|
||||
standardColourSpaces = [
|
||||
'linear', 'sRGB', 'rec709', 'Cineon', 'Gamma1.8', 'Gamma2.2',
|
||||
'Panalog', 'REDLog', 'ViperLog'
|
||||
"linear", "sRGB", "rec709", "Cineon", "Gamma1.8", "Gamma2.2",
|
||||
"Panalog", "REDLog", "ViperLog"
|
||||
]
|
||||
arriColourSpaces = [
|
||||
'Video - Rec709', 'LogC - Camera Native', 'Video - P3', 'ACES',
|
||||
'LogC - Film', 'LogC - Wide Gamut'
|
||||
"Video - Rec709", "LogC - Camera Native", "Video - P3", "ACES",
|
||||
"LogC - Film", "LogC - Wide Gamut"
|
||||
]
|
||||
r3dColourSpaces = [
|
||||
'Linear', 'Rec709', 'REDspace', 'REDlog', 'PDlog685', 'PDlog985',
|
||||
'CustomPDlog', 'REDgamma', 'SRGB', 'REDlogFilm', 'REDgamma2',
|
||||
'REDgamma3'
|
||||
"Linear", "Rec709", "REDspace", "REDlog", "PDlog685", "PDlog985",
|
||||
"CustomPDlog", "REDgamma", "SRGB", "REDlogFilm", "REDgamma2",
|
||||
"REDgamma3"
|
||||
]
|
||||
gColourSpaces = standardColourSpaces + arriColourSpaces + r3dColourSpaces
|
||||
|
||||
|
|
@ -97,52 +97,52 @@ class CustomSpreadsheetColumns(QObject):
|
|||
# This is the list of Columns available
|
||||
gCustomColumnList = [
|
||||
{
|
||||
'name': 'Tags',
|
||||
'cellType': 'readonly'
|
||||
"name": "Tags",
|
||||
"cellType": "readonly"
|
||||
},
|
||||
{
|
||||
'name': 'Colourspace',
|
||||
'cellType': 'dropdown'
|
||||
"name": "Colourspace",
|
||||
"cellType": "dropdown"
|
||||
},
|
||||
{
|
||||
'name': 'Notes',
|
||||
'cellType': 'readonly'
|
||||
"name": "Notes",
|
||||
"cellType": "readonly"
|
||||
},
|
||||
{
|
||||
'name': 'FileType',
|
||||
'cellType': 'readonly'
|
||||
"name": "FileType",
|
||||
"cellType": "readonly"
|
||||
},
|
||||
{
|
||||
'name': 'Shot Status',
|
||||
'cellType': 'dropdown'
|
||||
"name": "Shot Status",
|
||||
"cellType": "dropdown"
|
||||
},
|
||||
{
|
||||
'name': 'Thumbnail',
|
||||
'cellType': 'readonly'
|
||||
"name": "Thumbnail",
|
||||
"cellType": "readonly"
|
||||
},
|
||||
{
|
||||
'name': 'MediaType',
|
||||
'cellType': 'readonly'
|
||||
"name": "MediaType",
|
||||
"cellType": "readonly"
|
||||
},
|
||||
{
|
||||
'name': 'Width',
|
||||
'cellType': 'readonly'
|
||||
"name": "Width",
|
||||
"cellType": "readonly"
|
||||
},
|
||||
{
|
||||
'name': 'Height',
|
||||
'cellType': 'readonly'
|
||||
"name": "Height",
|
||||
"cellType": "readonly"
|
||||
},
|
||||
{
|
||||
'name': 'Pixel Aspect',
|
||||
'cellType': 'readonly'
|
||||
"name": "Pixel Aspect",
|
||||
"cellType": "readonly"
|
||||
},
|
||||
{
|
||||
'name': 'Artist',
|
||||
'cellType': 'dropdown'
|
||||
"name": "Artist",
|
||||
"cellType": "dropdown"
|
||||
},
|
||||
{
|
||||
'name': 'Department',
|
||||
'cellType': 'readonly'
|
||||
"name": "Department",
|
||||
"cellType": "readonly"
|
||||
},
|
||||
]
|
||||
|
||||
|
|
@ -156,7 +156,7 @@ class CustomSpreadsheetColumns(QObject):
|
|||
"""
|
||||
Return the name of a custom column
|
||||
"""
|
||||
return self.gCustomColumnList[column]['name']
|
||||
return self.gCustomColumnList[column]["name"]
|
||||
|
||||
def getTagsString(self, item):
|
||||
"""
|
||||
|
|
@ -173,7 +173,7 @@ class CustomSpreadsheetColumns(QObject):
|
|||
"""
|
||||
Convenience method for returning all the Notes in a Tag as a string
|
||||
"""
|
||||
notes = ''
|
||||
notes = ""
|
||||
tags = item.tags()
|
||||
for tag in tags:
|
||||
note = tag.note()
|
||||
|
|
@ -186,67 +186,67 @@ class CustomSpreadsheetColumns(QObject):
|
|||
Return the data in a cell
|
||||
"""
|
||||
currentColumn = self.gCustomColumnList[column]
|
||||
if currentColumn['name'] == 'Tags':
|
||||
if currentColumn["name"] == "Tags":
|
||||
return self.getTagsString(item)
|
||||
|
||||
if currentColumn['name'] == 'Colourspace':
|
||||
if currentColumn["name"] == "Colourspace":
|
||||
try:
|
||||
colTransform = item.sourceMediaColourTransform()
|
||||
except:
|
||||
colTransform = '--'
|
||||
colTransform = "--"
|
||||
return colTransform
|
||||
|
||||
if currentColumn['name'] == 'Notes':
|
||||
if currentColumn["name"] == "Notes":
|
||||
try:
|
||||
note = self.getNotes(item)
|
||||
except:
|
||||
note = ''
|
||||
note = ""
|
||||
return note
|
||||
|
||||
if currentColumn['name'] == 'FileType':
|
||||
fileType = '--'
|
||||
if currentColumn["name"] == "FileType":
|
||||
fileType = "--"
|
||||
M = item.source().mediaSource().metadata()
|
||||
if M.hasKey('foundry.source.type'):
|
||||
fileType = M.value('foundry.source.type')
|
||||
elif M.hasKey('media.input.filereader'):
|
||||
fileType = M.value('media.input.filereader')
|
||||
if M.hasKey("foundry.source.type"):
|
||||
fileType = M.value("foundry.source.type")
|
||||
elif M.hasKey("media.input.filereader"):
|
||||
fileType = M.value("media.input.filereader")
|
||||
return fileType
|
||||
|
||||
if currentColumn['name'] == 'Shot Status':
|
||||
if currentColumn["name"] == "Shot Status":
|
||||
status = item.status()
|
||||
if not status:
|
||||
status = "--"
|
||||
return str(status)
|
||||
|
||||
if currentColumn['name'] == 'MediaType':
|
||||
if currentColumn["name"] == "MediaType":
|
||||
M = item.mediaType()
|
||||
return str(M).split('MediaType')[-1].replace('.k', '')
|
||||
return str(M).split("MediaType")[-1].replace(".k", "")
|
||||
|
||||
if currentColumn['name'] == 'Thumbnail':
|
||||
if currentColumn["name"] == "Thumbnail":
|
||||
return str(item.eventNumber())
|
||||
|
||||
if currentColumn['name'] == 'Width':
|
||||
if currentColumn["name"] == "Width":
|
||||
return str(item.source().format().width())
|
||||
|
||||
if currentColumn['name'] == 'Height':
|
||||
if currentColumn["name"] == "Height":
|
||||
return str(item.source().format().height())
|
||||
|
||||
if currentColumn['name'] == 'Pixel Aspect':
|
||||
if currentColumn["name"] == "Pixel Aspect":
|
||||
return str(item.source().format().pixelAspect())
|
||||
|
||||
if currentColumn['name'] == 'Artist':
|
||||
if currentColumn["name"] == "Artist":
|
||||
if item.artist():
|
||||
name = item.artist()['artistName']
|
||||
name = item.artist()["artistName"]
|
||||
return name
|
||||
else:
|
||||
return '--'
|
||||
return "--"
|
||||
|
||||
if currentColumn['name'] == 'Department':
|
||||
if currentColumn["name"] == "Department":
|
||||
if item.artist():
|
||||
dep = item.artist()['artistDepartment']
|
||||
dep = item.artist()["artistDepartment"]
|
||||
return dep
|
||||
else:
|
||||
return '--'
|
||||
return "--"
|
||||
|
||||
return ""
|
||||
|
||||
|
|
@ -262,10 +262,10 @@ class CustomSpreadsheetColumns(QObject):
|
|||
Return the tooltip for a cell
|
||||
"""
|
||||
currentColumn = self.gCustomColumnList[column]
|
||||
if currentColumn['name'] == 'Tags':
|
||||
if currentColumn["name"] == "Tags":
|
||||
return str([item.name() for item in item.tags()])
|
||||
|
||||
if currentColumn['name'] == 'Notes':
|
||||
if currentColumn["name"] == "Notes":
|
||||
return str(self.getNotes(item))
|
||||
return ""
|
||||
|
||||
|
|
@ -296,24 +296,24 @@ class CustomSpreadsheetColumns(QObject):
|
|||
Return the icon for a cell
|
||||
"""
|
||||
currentColumn = self.gCustomColumnList[column]
|
||||
if currentColumn['name'] == 'Colourspace':
|
||||
if currentColumn["name"] == "Colourspace":
|
||||
return QIcon("icons:LUT.png")
|
||||
|
||||
if currentColumn['name'] == 'Shot Status':
|
||||
if currentColumn["name"] == "Shot Status":
|
||||
status = item.status()
|
||||
if status:
|
||||
return QIcon(gStatusTags[status])
|
||||
|
||||
if currentColumn['name'] == 'MediaType':
|
||||
if currentColumn["name"] == "MediaType":
|
||||
mediaType = item.mediaType()
|
||||
if mediaType == hiero.core.TrackItem.kVideo:
|
||||
return QIcon("icons:VideoOnly.png")
|
||||
elif mediaType == hiero.core.TrackItem.kAudio:
|
||||
return QIcon("icons:AudioOnly.png")
|
||||
|
||||
if currentColumn['name'] == 'Artist':
|
||||
if currentColumn["name"] == "Artist":
|
||||
try:
|
||||
return QIcon(item.artist()['artistIcon'])
|
||||
return QIcon(item.artist()["artistIcon"])
|
||||
except:
|
||||
return None
|
||||
return None
|
||||
|
|
@ -322,9 +322,9 @@ class CustomSpreadsheetColumns(QObject):
|
|||
"""
|
||||
Return the size hint for a cell
|
||||
"""
|
||||
currentColumnName = self.gCustomColumnList[column]['name']
|
||||
currentColumnName = self.gCustomColumnList[column]["name"]
|
||||
|
||||
if currentColumnName == 'Thumbnail':
|
||||
if currentColumnName == "Thumbnail":
|
||||
return QSize(90, 50)
|
||||
|
||||
return QSize(50, 50)
|
||||
|
|
@ -335,7 +335,7 @@ class CustomSpreadsheetColumns(QObject):
|
|||
with the default cell painting.
|
||||
"""
|
||||
currentColumn = self.gCustomColumnList[column]
|
||||
if currentColumn['name'] == 'Tags':
|
||||
if currentColumn["name"] == "Tags":
|
||||
if option.state & QStyle.State_Selected:
|
||||
painter.fillRect(option.rect, option.palette.highlight())
|
||||
iconSize = 20
|
||||
|
|
@ -348,14 +348,14 @@ class CustomSpreadsheetColumns(QObject):
|
|||
painter.setClipRect(option.rect)
|
||||
for tag in item.tags():
|
||||
M = tag.metadata()
|
||||
if not (M.hasKey('tag.status')
|
||||
or M.hasKey('tag.artistID')):
|
||||
if not (M.hasKey("tag.status")
|
||||
or M.hasKey("tag.artistID")):
|
||||
QIcon(tag.icon()).paint(painter, r, Qt.AlignLeft)
|
||||
r.translate(r.width() + 2, 0)
|
||||
painter.restore()
|
||||
return True
|
||||
|
||||
if currentColumn['name'] == 'Thumbnail':
|
||||
if currentColumn["name"] == "Thumbnail":
|
||||
imageView = None
|
||||
pen = QPen()
|
||||
r = QRect(option.rect.x() + 2, (option.rect.y() +
|
||||
|
|
@ -409,35 +409,35 @@ class CustomSpreadsheetColumns(QObject):
|
|||
self.currentView = view
|
||||
|
||||
currentColumn = self.gCustomColumnList[column]
|
||||
if currentColumn['cellType'] == 'readonly':
|
||||
if currentColumn["cellType"] == "readonly":
|
||||
cle = QLabel()
|
||||
cle.setEnabled(False)
|
||||
cle.setVisible(False)
|
||||
return cle
|
||||
|
||||
if currentColumn['name'] == 'Colourspace':
|
||||
if currentColumn["name"] == "Colourspace":
|
||||
cb = QComboBox()
|
||||
for colourspace in self.gColourSpaces:
|
||||
cb.addItem(colourspace)
|
||||
cb.currentIndexChanged.connect(self.colourspaceChanged)
|
||||
return cb
|
||||
|
||||
if currentColumn['name'] == 'Shot Status':
|
||||
if currentColumn["name"] == "Shot Status":
|
||||
cb = QComboBox()
|
||||
cb.addItem('')
|
||||
cb.addItem("")
|
||||
for key in gStatusTags.keys():
|
||||
cb.addItem(QIcon(gStatusTags[key]), key)
|
||||
cb.addItem('--')
|
||||
cb.addItem("--")
|
||||
cb.currentIndexChanged.connect(self.statusChanged)
|
||||
|
||||
return cb
|
||||
|
||||
if currentColumn['name'] == 'Artist':
|
||||
if currentColumn["name"] == "Artist":
|
||||
cb = QComboBox()
|
||||
cb.addItem('')
|
||||
cb.addItem("")
|
||||
for artist in gArtistList:
|
||||
cb.addItem(artist['artistName'])
|
||||
cb.addItem('--')
|
||||
cb.addItem(artist["artistName"])
|
||||
cb.addItem("--")
|
||||
cb.currentIndexChanged.connect(self.artistNameChanged)
|
||||
return cb
|
||||
return None
|
||||
|
|
@ -479,15 +479,15 @@ class CustomSpreadsheetColumns(QObject):
|
|||
status = self.sender().currentText()
|
||||
project = selection[0].project()
|
||||
with project.beginUndo("Set Status"):
|
||||
# A string of '--' characters denotes clear the status
|
||||
if status != '--':
|
||||
# A string of "--" characters denotes clear the status
|
||||
if status != "--":
|
||||
for trackItem in selection:
|
||||
trackItem.setStatus(status)
|
||||
else:
|
||||
for trackItem in selection:
|
||||
tTags = trackItem.tags()
|
||||
for tag in tTags:
|
||||
if tag.metadata().hasKey('tag.status'):
|
||||
if tag.metadata().hasKey("tag.status"):
|
||||
trackItem.removeTag(tag)
|
||||
break
|
||||
|
||||
|
|
@ -500,15 +500,15 @@ class CustomSpreadsheetColumns(QObject):
|
|||
name = self.sender().currentText()
|
||||
project = selection[0].project()
|
||||
with project.beginUndo("Assign Artist"):
|
||||
# A string of '--' denotes clear the assignee...
|
||||
if name != '--':
|
||||
# A string of "--" denotes clear the assignee...
|
||||
if name != "--":
|
||||
for trackItem in selection:
|
||||
trackItem.setArtistByName(name)
|
||||
else:
|
||||
for trackItem in selection:
|
||||
tTags = trackItem.tags()
|
||||
for tag in tTags:
|
||||
if tag.metadata().hasKey('tag.artistID'):
|
||||
if tag.metadata().hasKey("tag.artistID"):
|
||||
trackItem.removeTag(tag)
|
||||
break
|
||||
|
||||
|
|
@ -518,7 +518,7 @@ def _getArtistFromID(self, artistID):
|
|||
global gArtistList
|
||||
artist = [
|
||||
element for element in gArtistList
|
||||
if element['artistID'] == int(artistID)
|
||||
if element["artistID"] == int(artistID)
|
||||
]
|
||||
if not artist:
|
||||
return None
|
||||
|
|
@ -530,7 +530,7 @@ def _getArtistFromName(self, artistName):
|
|||
global gArtistList
|
||||
artist = [
|
||||
element for element in gArtistList
|
||||
if element['artistName'] == artistName
|
||||
if element["artistName"] == artistName
|
||||
]
|
||||
if not artist:
|
||||
return None
|
||||
|
|
@ -542,8 +542,8 @@ def _artist(self):
|
|||
artist = None
|
||||
tags = self.tags()
|
||||
for tag in tags:
|
||||
if tag.metadata().hasKey('tag.artistID'):
|
||||
artistID = tag.metadata().value('tag.artistID')
|
||||
if tag.metadata().hasKey("tag.artistID"):
|
||||
artistID = tag.metadata().value("tag.artistID")
|
||||
artist = self.getArtistFromID(artistID)
|
||||
return artist
|
||||
|
||||
|
|
@ -554,30 +554,30 @@ def _updateArtistTag(self, artistDict):
|
|||
artistTag = None
|
||||
tags = self.tags()
|
||||
for tag in tags:
|
||||
if tag.metadata().hasKey('tag.artistID'):
|
||||
if tag.metadata().hasKey("tag.artistID"):
|
||||
artistTag = tag
|
||||
break
|
||||
|
||||
if not artistTag:
|
||||
artistTag = hiero.core.Tag('Artist')
|
||||
artistTag.setIcon(artistDict['artistIcon'])
|
||||
artistTag.metadata().setValue('tag.artistID',
|
||||
str(artistDict['artistID']))
|
||||
artistTag.metadata().setValue('tag.artistName',
|
||||
str(artistDict['artistName']))
|
||||
artistTag.metadata().setValue('tag.artistDepartment',
|
||||
str(artistDict['artistDepartment']))
|
||||
artistTag = hiero.core.Tag("Artist")
|
||||
artistTag.setIcon(artistDict["artistIcon"])
|
||||
artistTag.metadata().setValue("tag.artistID",
|
||||
str(artistDict["artistID"]))
|
||||
artistTag.metadata().setValue("tag.artistName",
|
||||
str(artistDict["artistName"]))
|
||||
artistTag.metadata().setValue("tag.artistDepartment",
|
||||
str(artistDict["artistDepartment"]))
|
||||
self.sequence().editFinished()
|
||||
self.addTag(artistTag)
|
||||
self.sequence().editFinished()
|
||||
return
|
||||
|
||||
artistTag.setIcon(artistDict['artistIcon'])
|
||||
artistTag.metadata().setValue('tag.artistID', str(artistDict['artistID']))
|
||||
artistTag.metadata().setValue('tag.artistName',
|
||||
str(artistDict['artistName']))
|
||||
artistTag.metadata().setValue('tag.artistDepartment',
|
||||
str(artistDict['artistDepartment']))
|
||||
artistTag.setIcon(artistDict["artistIcon"])
|
||||
artistTag.metadata().setValue("tag.artistID", str(artistDict["artistID"]))
|
||||
artistTag.metadata().setValue("tag.artistName",
|
||||
str(artistDict["artistName"]))
|
||||
artistTag.metadata().setValue("tag.artistDepartment",
|
||||
str(artistDict["artistDepartment"]))
|
||||
self.sequence().editFinished()
|
||||
return
|
||||
|
||||
|
|
@ -588,8 +588,9 @@ def _setArtistByName(self, artistName):
|
|||
|
||||
artist = self.getArtistFromName(artistName)
|
||||
if not artist:
|
||||
print 'Artist name: %s was not found in the gArtistList.' % str(
|
||||
artistName)
|
||||
print((
|
||||
"Artist name: {} was not found in "
|
||||
"the gArtistList.").format(artistName))
|
||||
return
|
||||
|
||||
# Do the update.
|
||||
|
|
@ -602,8 +603,8 @@ def _setArtistByID(self, artistID):
|
|||
|
||||
artist = self.getArtistFromID(artistID)
|
||||
if not artist:
|
||||
print 'Artist name: %s was not found in the gArtistList.' % str(
|
||||
artistID)
|
||||
print("Artist name: {} was not found in the gArtistList.".format(
|
||||
artistID))
|
||||
return
|
||||
|
||||
# Do the update.
|
||||
|
|
@ -625,15 +626,15 @@ def _status(self):
|
|||
status = None
|
||||
tags = self.tags()
|
||||
for tag in tags:
|
||||
if tag.metadata().hasKey('tag.status'):
|
||||
status = tag.metadata().value('tag.status')
|
||||
if tag.metadata().hasKey("tag.status"):
|
||||
status = tag.metadata().value("tag.status")
|
||||
return status
|
||||
|
||||
|
||||
def _setStatus(self, status):
|
||||
"""setShotStatus(status) -> Method to set the Status of a Shot.
|
||||
Adds a special kind of status Tag to a TrackItem
|
||||
Example: myTrackItem.setStatus('Final')
|
||||
Example: myTrackItem.setStatus("Final")
|
||||
|
||||
@param status - a string, corresponding to the Status name
|
||||
"""
|
||||
|
|
@ -641,25 +642,25 @@ def _setStatus(self, status):
|
|||
|
||||
# Get a valid Tag object from the Global list of statuses
|
||||
if not status in gStatusTags.keys():
|
||||
print 'Status requested was not a valid Status string.'
|
||||
print("Status requested was not a valid Status string.")
|
||||
return
|
||||
|
||||
# A shot should only have one status. Check if one exists and set accordingly
|
||||
statusTag = None
|
||||
tags = self.tags()
|
||||
for tag in tags:
|
||||
if tag.metadata().hasKey('tag.status'):
|
||||
if tag.metadata().hasKey("tag.status"):
|
||||
statusTag = tag
|
||||
break
|
||||
|
||||
if not statusTag:
|
||||
statusTag = hiero.core.Tag('Status')
|
||||
statusTag = hiero.core.Tag("Status")
|
||||
statusTag.setIcon(gStatusTags[status])
|
||||
statusTag.metadata().setValue('tag.status', status)
|
||||
statusTag.metadata().setValue("tag.status", status)
|
||||
self.addTag(statusTag)
|
||||
|
||||
statusTag.setIcon(gStatusTags[status])
|
||||
statusTag.metadata().setValue('tag.status', status)
|
||||
statusTag.metadata().setValue("tag.status", status)
|
||||
|
||||
self.sequence().editFinished()
|
||||
return
|
||||
|
|
@ -743,7 +744,7 @@ class SetStatusMenu(QMenu):
|
|||
|
||||
# This handles events from the Project Bin View
|
||||
def eventHandler(self, event):
|
||||
if not hasattr(event.sender, 'selection'):
|
||||
if not hasattr(event.sender, "selection"):
|
||||
# Something has gone wrong, we should only be here if raised
|
||||
# by the Timeline/Spreadsheet view which gives a selection.
|
||||
return
|
||||
|
|
@ -781,9 +782,9 @@ class AssignArtistMenu(QMenu):
|
|||
for artist in self.artists:
|
||||
self.menuActions += [
|
||||
titleStringTriggeredAction(
|
||||
artist['artistName'],
|
||||
artist["artistName"],
|
||||
self.setArtistFromMenuSelection,
|
||||
icon=artist['artistIcon'])
|
||||
icon=artist["artistIcon"])
|
||||
]
|
||||
|
||||
def setArtistFromMenuSelection(self, menuSelectionArtist):
|
||||
|
|
@ -818,7 +819,7 @@ class AssignArtistMenu(QMenu):
|
|||
|
||||
# This handles events from the Project Bin View
|
||||
def eventHandler(self, event):
|
||||
if not hasattr(event.sender, 'selection'):
|
||||
if not hasattr(event.sender, "selection"):
|
||||
# Something has gone wrong, we should only be here if raised
|
||||
# by the Timeline/Spreadsheet view which gives a selection.
|
||||
return
|
||||
|
|
@ -833,7 +834,7 @@ class AssignArtistMenu(QMenu):
|
|||
event.menu.addMenu(self)
|
||||
|
||||
|
||||
# Add the 'Set Status' context menu to Timeline and Spreadsheet
|
||||
# Add the "Set Status" context menu to Timeline and Spreadsheet
|
||||
if kAddStatusMenu:
|
||||
setStatusMenu = SetStatusMenu()
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ class PurgeUnusedAction(QAction):
|
|||
self.triggered.connect(self.PurgeUnused)
|
||||
hiero.core.events.registerInterest("kShowContextMenu/kBin",
|
||||
self.eventHandler)
|
||||
self.setIcon(QIcon('icons:TagDelete.png'))
|
||||
self.setIcon(QIcon("icons:TagDelete.png"))
|
||||
|
||||
# Method to return whether a Bin is empty...
|
||||
def binIsEmpty(self, b):
|
||||
|
|
@ -67,19 +67,19 @@ class PurgeUnusedAction(QAction):
|
|||
msgBox.setDefaultButton(QMessageBox.Ok)
|
||||
ret = msgBox.exec_()
|
||||
if ret == QMessageBox.Cancel:
|
||||
print 'Not purging anything.'
|
||||
print("Not purging anything.")
|
||||
elif ret == QMessageBox.Ok:
|
||||
with proj.beginUndo('Purge Unused Clips'):
|
||||
with proj.beginUndo("Purge Unused Clips"):
|
||||
BINS = []
|
||||
for clip in CLIPSTOREMOVE:
|
||||
BI = clip.binItem()
|
||||
B = BI.parentBin()
|
||||
BINS += [B]
|
||||
print 'Removing:', BI
|
||||
print("Removing: {}".format(BI))
|
||||
try:
|
||||
B.removeItem(BI)
|
||||
except:
|
||||
print 'Unable to remove: ' + BI
|
||||
print("Unable to remove: {}".format(BI))
|
||||
return
|
||||
|
||||
# For each sequence, iterate through each track Item, see if the Clip is in the CLIPS list.
|
||||
|
|
@ -104,24 +104,24 @@ class PurgeUnusedAction(QAction):
|
|||
ret = msgBox.exec_()
|
||||
|
||||
if ret == QMessageBox.Cancel:
|
||||
print 'Cancel'
|
||||
print("Cancel")
|
||||
return
|
||||
elif ret == QMessageBox.Ok:
|
||||
BINS = []
|
||||
with proj.beginUndo('Purge Unused Clips'):
|
||||
with proj.beginUndo("Purge Unused Clips"):
|
||||
# Delete the rest of the Clips
|
||||
for clip in CLIPSTOREMOVE:
|
||||
BI = clip.binItem()
|
||||
B = BI.parentBin()
|
||||
BINS += [B]
|
||||
print 'Removing:', BI
|
||||
print("Removing: {}".format(BI))
|
||||
try:
|
||||
B.removeItem(BI)
|
||||
except:
|
||||
print 'Unable to remove: ' + BI
|
||||
print("Unable to remove: {}".format(BI))
|
||||
|
||||
def eventHandler(self, event):
|
||||
if not hasattr(event.sender, 'selection'):
|
||||
if not hasattr(event.sender, "selection"):
|
||||
# Something has gone wrong, we shouldn't only be here if raised
|
||||
# by the Bin view which will give a selection.
|
||||
return
|
||||
|
|
@ -13,21 +13,22 @@ except:
|
|||
|
||||
#----------------------------------------------
|
||||
a = hiero.ui.findMenuAction('Import File(s)...')
|
||||
# Note: You probably best to make this 'Ctrl+R' - currently conflicts with 'Red' in the Viewer!
|
||||
a.setShortcut(QKeySequence('R'))
|
||||
# Note: You probably best to make this 'Ctrl+R' - currently conflicts with "Red" in the Viewer!
|
||||
a.setShortcut(QKeySequence("R"))
|
||||
#----------------------------------------------
|
||||
a = hiero.ui.findMenuAction('Import Folder(s)...')
|
||||
a.setShortcut(QKeySequence('Shift+R'))
|
||||
#----------------------------------------------
|
||||
a = hiero.ui.findMenuAction('Import EDL/XML/AAF...')
|
||||
a = hiero.ui.findMenuAction("Import EDL/XML/AAF...")
|
||||
a.setShortcut(QKeySequence('Ctrl+Shift+O'))
|
||||
#----------------------------------------------
|
||||
a = hiero.ui.findMenuAction('Metadata View')
|
||||
a.setShortcut(QKeySequence('I'))
|
||||
a = hiero.ui.findMenuAction("Metadata View")
|
||||
a.setShortcut(QKeySequence("I"))
|
||||
#----------------------------------------------
|
||||
a = hiero.ui.findMenuAction('Edit Settings')
|
||||
a.setShortcut(QKeySequence('S'))
|
||||
a = hiero.ui.findMenuAction("Edit Settings")
|
||||
a.setShortcut(QKeySequence("S"))
|
||||
#----------------------------------------------
|
||||
a = hiero.ui.findMenuAction('Monitor Output')
|
||||
a.setShortcut(QKeySequence('Ctrl+U'))
|
||||
a = hiero.ui.findMenuAction("Monitor Output")
|
||||
if a:
|
||||
a.setShortcut(QKeySequence('Ctrl+U'))
|
||||
#----------------------------------------------
|
||||
|
|
@ -44,16 +44,16 @@ def get_transition_type(otio_item, otio_track):
|
|||
_out = None
|
||||
|
||||
if _in and _out:
|
||||
return 'dissolve'
|
||||
return "dissolve"
|
||||
|
||||
elif _in and not _out:
|
||||
return 'fade_out'
|
||||
return "fade_out"
|
||||
|
||||
elif not _in and _out:
|
||||
return 'fade_in'
|
||||
return "fade_in"
|
||||
|
||||
else:
|
||||
return 'unknown'
|
||||
return "unknown"
|
||||
|
||||
|
||||
def find_trackitem(name, hiero_track):
|
||||
|
|
@ -84,10 +84,10 @@ def apply_transition(otio_track, otio_item, track):
|
|||
|
||||
# Figure out track kind for getattr below
|
||||
if isinstance(track, hiero.core.VideoTrack):
|
||||
kind = ''
|
||||
kind = ""
|
||||
|
||||
else:
|
||||
kind = 'Audio'
|
||||
kind = "Audio"
|
||||
|
||||
try:
|
||||
# Gather TrackItems involved in trasition
|
||||
|
|
@ -98,7 +98,7 @@ def apply_transition(otio_track, otio_item, track):
|
|||
)
|
||||
|
||||
# Create transition object
|
||||
if transition_type == 'dissolve':
|
||||
if transition_type == "dissolve":
|
||||
transition_func = getattr(
|
||||
hiero.core.Transition,
|
||||
'create{kind}DissolveTransition'.format(kind=kind)
|
||||
|
|
@ -111,7 +111,7 @@ def apply_transition(otio_track, otio_item, track):
|
|||
otio_item.out_offset.value
|
||||
)
|
||||
|
||||
elif transition_type == 'fade_in':
|
||||
elif transition_type == "fade_in":
|
||||
transition_func = getattr(
|
||||
hiero.core.Transition,
|
||||
'create{kind}FadeInTransition'.format(kind=kind)
|
||||
|
|
@ -121,7 +121,7 @@ def apply_transition(otio_track, otio_item, track):
|
|||
otio_item.out_offset.value
|
||||
)
|
||||
|
||||
elif transition_type == 'fade_out':
|
||||
elif transition_type == "fade_out":
|
||||
transition_func = getattr(
|
||||
hiero.core.Transition,
|
||||
'create{kind}FadeOutTransition'.format(kind=kind)
|
||||
|
|
@ -150,11 +150,11 @@ def apply_transition(otio_track, otio_item, track):
|
|||
def prep_url(url_in):
|
||||
url = unquote(url_in)
|
||||
|
||||
if url.startswith('file://localhost/'):
|
||||
return url.replace('file://localhost/', '')
|
||||
if url.startswith("file://localhost/"):
|
||||
return url.replace("file://localhost/", "")
|
||||
|
||||
url = '{url}'.format(
|
||||
sep=url.startswith(os.sep) and '' or os.sep,
|
||||
sep=url.startswith(os.sep) and "" or os.sep,
|
||||
url=url.startswith(os.sep) and url[1:] or url
|
||||
)
|
||||
|
||||
|
|
@ -228,8 +228,8 @@ def add_metadata(metadata, hiero_item):
|
|||
continue
|
||||
|
||||
if value is not None:
|
||||
if not key.startswith('tag.'):
|
||||
key = 'tag.' + key
|
||||
if not key.startswith("tag."):
|
||||
key = "tag." + key
|
||||
|
||||
hiero_item.metadata().setValue(key, str(value))
|
||||
|
||||
|
|
@ -371,10 +371,10 @@ def build_sequence(
|
|||
|
||||
if not sequence:
|
||||
# Create a Sequence
|
||||
sequence = hiero.core.Sequence(otio_timeline.name or 'OTIOSequence')
|
||||
sequence = hiero.core.Sequence(otio_timeline.name or "OTIOSequence")
|
||||
|
||||
# Set sequence settings from otio timeline if available
|
||||
if hasattr(otio_timeline, 'global_start_time'):
|
||||
if hasattr(otio_timeline, "global_start_time"):
|
||||
if otio_timeline.global_start_time:
|
||||
start_time = otio_timeline.global_start_time
|
||||
sequence.setFramerate(start_time.rate)
|
||||
|
|
@ -414,7 +414,7 @@ def build_sequence(
|
|||
if isinstance(otio_clip, otio.schema.Stack):
|
||||
bar = hiero.ui.mainWindow().statusBar()
|
||||
bar.showMessage(
|
||||
'Nested sequences are created separately.',
|
||||
"Nested sequences are created separately.",
|
||||
timeout=3000
|
||||
)
|
||||
build_sequence(otio_clip, project, otio_track.kind)
|
||||
|
|
@ -9,19 +9,19 @@ import hiero.core
|
|||
|
||||
import PySide2.QtWidgets as qw
|
||||
|
||||
from openpype.hosts.hiero.otio.hiero_import import load_otio
|
||||
from openpype.hosts.hiero.api.otio.hiero_import import load_otio
|
||||
|
||||
|
||||
class OTIOProjectSelect(qw.QDialog):
|
||||
|
||||
def __init__(self, projects, *args, **kwargs):
|
||||
super(OTIOProjectSelect, self).__init__(*args, **kwargs)
|
||||
self.setWindowTitle('Please select active project')
|
||||
self.setWindowTitle("Please select active project")
|
||||
self.layout = qw.QVBoxLayout()
|
||||
|
||||
self.label = qw.QLabel(
|
||||
'Unable to determine which project to import sequence to.\n'
|
||||
'Please select one.'
|
||||
"Unable to determine which project to import sequence to.\n"
|
||||
"Please select one."
|
||||
)
|
||||
self.layout.addWidget(self.label)
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ def get_sequence(view):
|
|||
|
||||
elif isinstance(view, hiero.ui.BinView):
|
||||
for item in view.selection():
|
||||
if not hasattr(item, 'acitveItem'):
|
||||
if not hasattr(item, "acitveItem"):
|
||||
continue
|
||||
|
||||
if isinstance(item.activeItem(), hiero.core.Sequence):
|
||||
|
|
@ -57,13 +57,13 @@ def get_sequence(view):
|
|||
def OTIO_menu_action(event):
|
||||
# Menu actions
|
||||
otio_import_action = hiero.ui.createMenuAction(
|
||||
'Import OTIO...',
|
||||
"Import OTIO...",
|
||||
open_otio_file,
|
||||
icon=None
|
||||
)
|
||||
|
||||
otio_add_track_action = hiero.ui.createMenuAction(
|
||||
'New Track(s) from OTIO...',
|
||||
"New Track(s) from OTIO...",
|
||||
open_otio_file,
|
||||
icon=None
|
||||
)
|
||||
|
|
@ -80,19 +80,19 @@ def OTIO_menu_action(event):
|
|||
otio_add_track_action.setEnabled(True)
|
||||
|
||||
for action in event.menu.actions():
|
||||
if action.text() == 'Import':
|
||||
if action.text() == "Import":
|
||||
action.menu().addAction(otio_import_action)
|
||||
action.menu().addAction(otio_add_track_action)
|
||||
|
||||
elif action.text() == 'New Track':
|
||||
elif action.text() == "New Track":
|
||||
action.menu().addAction(otio_add_track_action)
|
||||
|
||||
|
||||
def open_otio_file():
|
||||
files = hiero.ui.openFileBrowser(
|
||||
caption='Please select an OTIO file of choice',
|
||||
pattern='*.otio',
|
||||
requiredExtension='.otio'
|
||||
caption="Please select an OTIO file of choice",
|
||||
pattern="*.otio",
|
||||
requiredExtension=".otio"
|
||||
)
|
||||
|
||||
selection = None
|
||||
|
|
@ -117,7 +117,7 @@ def open_otio_file():
|
|||
else:
|
||||
bar = hiero.ui.mainWindow().statusBar()
|
||||
bar.showMessage(
|
||||
'OTIO Import aborted by user',
|
||||
"OTIO Import aborted by user",
|
||||
timeout=3000
|
||||
)
|
||||
return
|
||||
|
|
@ -10,15 +10,15 @@ except:
|
|||
|
||||
|
||||
def setPosterFrame(posterFrame=.5):
|
||||
'''
|
||||
"""
|
||||
Update the poster frame of the given clipItmes
|
||||
posterFrame = .5 uses the centre frame, a value of 0 uses the first frame, a value of 1 uses the last frame
|
||||
'''
|
||||
"""
|
||||
view = hiero.ui.activeView()
|
||||
|
||||
selectedBinItems = view.selection()
|
||||
selectedClipItems = [(item.activeItem()
|
||||
if hasattr(item, 'activeItem') else item)
|
||||
if hasattr(item, "activeItem") else item)
|
||||
for item in selectedBinItems]
|
||||
|
||||
for clip in selectedClipItems:
|
||||
|
|
@ -65,13 +65,9 @@ def open_file(filepath):
|
|||
|
||||
def current_file():
|
||||
current_file = hiero.core.projects()[-1].path()
|
||||
normalised = os.path.normpath(current_file)
|
||||
|
||||
# Unsaved current file
|
||||
if normalised == "":
|
||||
if not current_file:
|
||||
return None
|
||||
|
||||
return normalised
|
||||
return os.path.normpath(current_file)
|
||||
|
||||
|
||||
def work_root(session):
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import pyblish
|
||||
import openpype
|
||||
from openpype.hosts.hiero import api as phiero
|
||||
from openpype.hosts.hiero.otio import hiero_export
|
||||
from openpype.hosts.hiero.api.otio import hiero_export
|
||||
import hiero
|
||||
|
||||
from compiler.ast import flatten
|
||||
|
||||
# # developer reload modules
|
||||
from pprint import pformat
|
||||
|
||||
|
|
@ -339,10 +337,10 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
|||
continue
|
||||
|
||||
track_index = track.trackIndex()
|
||||
_sub_track_items = flatten(track.subTrackItems())
|
||||
_sub_track_items = phiero.flatten(track.subTrackItems())
|
||||
|
||||
# continue only if any subtrack items are collected
|
||||
if len(_sub_track_items) < 1:
|
||||
if not list(_sub_track_items):
|
||||
continue
|
||||
|
||||
enabled_sti = []
|
||||
|
|
@ -357,7 +355,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
|||
enabled_sti.append(_sti)
|
||||
|
||||
# continue only if any subtrack items are collected
|
||||
if len(enabled_sti) < 1:
|
||||
if not enabled_sti:
|
||||
continue
|
||||
|
||||
# add collection of subtrackitems to dict
|
||||
|
|
@ -371,7 +369,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
|||
Returns list of Clip's hiero.core.Annotation
|
||||
"""
|
||||
annotations = []
|
||||
subTrackItems = flatten(clip.subTrackItems())
|
||||
subTrackItems = phiero.flatten(clip.subTrackItems())
|
||||
annotations += [item for item in subTrackItems if isinstance(
|
||||
item, hiero.core.Annotation)]
|
||||
return annotations
|
||||
|
|
@ -382,7 +380,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
|||
Returns list of Clip's hiero.core.SubTrackItem
|
||||
"""
|
||||
subtracks = []
|
||||
subTrackItems = flatten(clip.parent().subTrackItems())
|
||||
subTrackItems = phiero.flatten(clip.parent().subTrackItems())
|
||||
for item in subTrackItems:
|
||||
if "TimeWarp" in item.name():
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import hiero.ui
|
|||
from openpype.hosts.hiero import api as phiero
|
||||
from avalon import api as avalon
|
||||
from pprint import pformat
|
||||
from openpype.hosts.hiero.otio import hiero_export
|
||||
from openpype.hosts.hiero.api.otio import hiero_export
|
||||
from Qt.QtGui import QPixmap
|
||||
import tempfile
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from pyblish import api
|
||||
import hiero
|
||||
import math
|
||||
from openpype.hosts.hiero.otio.hiero_export import create_otio_time_range
|
||||
from openpype.hosts.hiero.api.otio.hiero_export import create_otio_time_range
|
||||
|
||||
class PrecollectRetime(api.InstancePlugin):
|
||||
"""Calculate Retiming of selected track items."""
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
from OTIOExportTask import OTIOExportTask
|
||||
from OTIOExportUI import OTIOExportUI
|
||||
|
||||
__all__ = [
|
||||
'OTIOExportTask',
|
||||
'OTIOExportUI'
|
||||
]
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
"""Puts the selection project into 'hiero.selection'"""
|
||||
|
||||
import hiero
|
||||
|
||||
|
||||
def selectionChanged(event):
|
||||
hiero.selection = event.sender.selection()
|
||||
|
||||
hiero.core.events.registerInterest('kSelectionChanged', selectionChanged)
|
||||
|
|
@ -3,9 +3,10 @@
|
|||
import contextlib
|
||||
|
||||
import logging
|
||||
from Qt import QtCore, QtGui
|
||||
from openpype.tools.utils.widgets import AssetWidget
|
||||
from avalon import style, io
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
from avalon import io
|
||||
from openpype import style
|
||||
from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget
|
||||
|
||||
from pxr import Sdf
|
||||
|
||||
|
|
@ -13,6 +14,60 @@ from pxr import Sdf
|
|||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SelectAssetDialog(QtWidgets.QWidget):
|
||||
"""Frameless assets dialog to select asset with double click.
|
||||
|
||||
Args:
|
||||
parm: Parameter where selected asset name is set.
|
||||
"""
|
||||
def __init__(self, parm):
|
||||
self.setWindowTitle("Pick Asset")
|
||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Popup)
|
||||
|
||||
assets_widget = SingleSelectAssetsWidget(io, parent=self)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.addWidget(assets_widget)
|
||||
|
||||
assets_widget.double_clicked.connect(self._set_parameter)
|
||||
self._assets_widget = assets_widget
|
||||
self._parm = parm
|
||||
|
||||
def _set_parameter(self):
|
||||
name = self._assets_widget.get_selected_asset_name()
|
||||
self._parm.set(name)
|
||||
self.close()
|
||||
|
||||
def _on_show(self):
|
||||
pos = QtGui.QCursor.pos()
|
||||
# Select the current asset if there is any
|
||||
select_id = None
|
||||
name = self._parm.eval()
|
||||
if name:
|
||||
db_asset = io.find_one(
|
||||
{"name": name, "type": "asset"},
|
||||
{"_id": True}
|
||||
)
|
||||
if db_asset:
|
||||
select_id = db_asset["_id"]
|
||||
|
||||
# Set stylesheet
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
# Refresh assets (is threaded)
|
||||
self._assets_widget.refresh()
|
||||
# Select asset - must be done after refresh
|
||||
if select_id is not None:
|
||||
self._assets_widget.select_asset(select_id)
|
||||
|
||||
# Show cursor (top right of window) near cursor
|
||||
self.resize(250, 400)
|
||||
self.move(self.mapFromGlobal(pos) - QtCore.QPoint(self.width(), 0))
|
||||
|
||||
def showEvent(self, event):
|
||||
super(SelectAssetDialog, self).showEvent(event)
|
||||
self._on_show()
|
||||
|
||||
|
||||
def pick_asset(node):
|
||||
"""Show a user interface to select an Asset in the project
|
||||
|
||||
|
|
@ -21,43 +76,15 @@ def pick_asset(node):
|
|||
|
||||
"""
|
||||
|
||||
pos = QtGui.QCursor.pos()
|
||||
|
||||
parm = node.parm("asset_name")
|
||||
if not parm:
|
||||
log.error("Node has no 'asset' parameter: %s", node)
|
||||
return
|
||||
|
||||
# Construct the AssetWidget as a frameless popup so it automatically
|
||||
# Construct a frameless popup so it automatically
|
||||
# closes when clicked outside of it.
|
||||
global tool
|
||||
tool = AssetWidget(io)
|
||||
tool.setContentsMargins(5, 5, 5, 5)
|
||||
tool.setWindowTitle("Pick Asset")
|
||||
tool.setStyleSheet(style.load_stylesheet())
|
||||
tool.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Popup)
|
||||
tool.refresh()
|
||||
|
||||
# Select the current asset if there is any
|
||||
name = parm.eval()
|
||||
if name:
|
||||
db_asset = io.find_one({"name": name, "type": "asset"})
|
||||
if db_asset:
|
||||
silo = db_asset.get("silo")
|
||||
if silo:
|
||||
tool.set_silo(silo)
|
||||
tool.select_assets([name], expand=True)
|
||||
|
||||
# Show cursor (top right of window) near cursor
|
||||
tool.resize(250, 400)
|
||||
tool.move(tool.mapFromGlobal(pos) - QtCore.QPoint(tool.width(), 0))
|
||||
|
||||
def set_parameter_callback(index):
|
||||
name = index.data(tool.model.DocumentRole)["name"]
|
||||
parm.set(name)
|
||||
tool.close()
|
||||
|
||||
tool.view.doubleClicked.connect(set_parameter_callback)
|
||||
tool = SelectAssetDialog(parm)
|
||||
tool.show()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,16 @@ from avalon.houdini import pipeline
|
|||
pipeline.reload_pipeline()]]></scriptCode>
|
||||
</scriptItem>
|
||||
</subMenu>
|
||||
|
||||
<separatorItem/>
|
||||
<scriptItem id="experimental_tools">
|
||||
<label>Experimental tools...</label>
|
||||
<scriptCode><![CDATA[
|
||||
import hou
|
||||
from openpype.tools.utils import host_tools
|
||||
parent = hou.qt.mainWindow()
|
||||
host_tools.show_experimental_tools_dialog(parent)]]></scriptCode>
|
||||
</scriptItem>
|
||||
</subMenu>
|
||||
</menuBar>
|
||||
</mainMenu>
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ def on_save(_):
|
|||
def on_open(_):
|
||||
"""On scene open let's assume the containers have changed."""
|
||||
|
||||
from avalon.vendor.Qt import QtWidgets
|
||||
from Qt import QtWidgets
|
||||
from openpype.widgets import popup
|
||||
|
||||
cmds.evalDeferred(
|
||||
|
|
|
|||
|
|
@ -6,19 +6,19 @@ import platform
|
|||
import uuid
|
||||
import math
|
||||
|
||||
import bson
|
||||
import json
|
||||
import logging
|
||||
import itertools
|
||||
import contextlib
|
||||
from collections import OrderedDict, defaultdict
|
||||
from math import ceil
|
||||
from six import string_types
|
||||
import bson
|
||||
|
||||
from maya import cmds, mel
|
||||
import maya.api.OpenMaya as om
|
||||
|
||||
from avalon import api, maya, io, pipeline
|
||||
from avalon.vendor.six import string_types
|
||||
import avalon.maya.lib
|
||||
import avalon.maya.interactive
|
||||
|
||||
|
|
@ -1936,7 +1936,7 @@ def validate_fps():
|
|||
|
||||
if current_fps != fps:
|
||||
|
||||
from avalon.vendor.Qt import QtWidgets
|
||||
from Qt import QtWidgets
|
||||
from ...widgets import popup
|
||||
|
||||
# Find maya main window
|
||||
|
|
@ -2694,7 +2694,7 @@ def update_content_on_context_change():
|
|||
|
||||
|
||||
def show_message(title, msg):
|
||||
from avalon.vendor.Qt import QtWidgets
|
||||
from Qt import QtWidgets
|
||||
from openpype.widgets import message_window
|
||||
|
||||
# Find maya main window
|
||||
|
|
|
|||
|
|
@ -180,6 +180,7 @@ class ARenderProducts:
|
|||
self.layer = layer
|
||||
self.render_instance = render_instance
|
||||
self.multipart = False
|
||||
self.aov_separator = render_instance.data.get("aovSeparator", "_")
|
||||
|
||||
# Initialize
|
||||
self.layer_data = self._get_layer_data()
|
||||
|
|
@ -676,7 +677,7 @@ class RenderProductsVray(ARenderProducts):
|
|||
|
||||
"""
|
||||
prefix = super(RenderProductsVray, self).get_renderer_prefix()
|
||||
prefix = "{}.<aov>".format(prefix)
|
||||
prefix = "{}{}<aov>".format(prefix, self.aov_separator)
|
||||
return prefix
|
||||
|
||||
def _get_layer_data(self):
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from openpype.api import (
|
|||
from openpype.modules import ModulesManager
|
||||
|
||||
from avalon.api import Session
|
||||
from avalon.api import CreatorError
|
||||
|
||||
|
||||
class CreateRender(plugin.Creator):
|
||||
|
|
@ -81,13 +82,21 @@ class CreateRender(plugin.Creator):
|
|||
}
|
||||
|
||||
_image_prefixes = {
|
||||
'mentalray': 'maya/<Scene>/<RenderLayer>/<RenderLayer>_<RenderPass>',
|
||||
'mentalray': 'maya/<Scene>/<RenderLayer>/<RenderLayer>{aov_separator}<RenderPass>', # noqa
|
||||
'vray': 'maya/<scene>/<Layer>/<Layer>',
|
||||
'arnold': 'maya/<Scene>/<RenderLayer>/<RenderLayer>_<RenderPass>',
|
||||
'renderman': 'maya/<Scene>/<layer>/<layer>_<aov>',
|
||||
'redshift': 'maya/<Scene>/<RenderLayer>/<RenderLayer>_<RenderPass>'
|
||||
'arnold': 'maya/<Scene>/<RenderLayer>/<RenderLayer>{aov_separator}<RenderPass>', # noqa
|
||||
'renderman': 'maya/<Scene>/<layer>/<layer>{aov_separator}<aov>',
|
||||
'redshift': 'maya/<Scene>/<RenderLayer>/<RenderLayer>{aov_separator}<RenderPass>' # noqa
|
||||
}
|
||||
|
||||
_aov_chars = {
|
||||
"dot": ".",
|
||||
"dash": "-",
|
||||
"underscore": "_"
|
||||
}
|
||||
|
||||
_project_settings = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Constructor."""
|
||||
super(CreateRender, self).__init__(*args, **kwargs)
|
||||
|
|
@ -95,12 +104,24 @@ class CreateRender(plugin.Creator):
|
|||
if not deadline_settings["enabled"]:
|
||||
self.deadline_servers = {}
|
||||
return
|
||||
project_settings = get_project_settings(Session["AVALON_PROJECT"])
|
||||
self._project_settings = get_project_settings(
|
||||
Session["AVALON_PROJECT"])
|
||||
|
||||
# project_settings/maya/create/CreateRender/aov_separator
|
||||
try:
|
||||
self.aov_separator = self._aov_chars[(
|
||||
self._project_settings["maya"]
|
||||
["create"]
|
||||
["CreateRender"]
|
||||
["aov_separator"]
|
||||
)]
|
||||
except KeyError:
|
||||
self.aov_separator = "_"
|
||||
|
||||
try:
|
||||
default_servers = deadline_settings["deadline_urls"]
|
||||
project_servers = (
|
||||
project_settings["deadline"]
|
||||
["deadline_servers"]
|
||||
self._project_settings["deadline"]["deadline_servers"]
|
||||
)
|
||||
self.deadline_servers = {
|
||||
k: default_servers[k]
|
||||
|
|
@ -409,8 +430,10 @@ class CreateRender(plugin.Creator):
|
|||
renderer (str): Renderer name.
|
||||
|
||||
"""
|
||||
prefix = self._image_prefixes[renderer]
|
||||
prefix = prefix.replace("{aov_separator}", self.aov_separator)
|
||||
cmds.setAttr(self._image_prefix_nodes[renderer],
|
||||
self._image_prefixes[renderer],
|
||||
prefix,
|
||||
type="string")
|
||||
|
||||
asset = get_asset()
|
||||
|
|
@ -446,37 +469,37 @@ class CreateRender(plugin.Creator):
|
|||
|
||||
self._set_global_output_settings()
|
||||
|
||||
@staticmethod
|
||||
def _set_renderer_option(renderer_node, arg=None, value=None):
|
||||
# type: (str, str, str) -> str
|
||||
"""Set option on renderer node.
|
||||
|
||||
If renderer settings node doesn't exists, it is created first.
|
||||
|
||||
Args:
|
||||
renderer_node (str): Renderer name.
|
||||
arg (str, optional): Argument name.
|
||||
value (str, optional): Argument value.
|
||||
|
||||
Returns:
|
||||
str: Renderer settings node.
|
||||
|
||||
"""
|
||||
settings = cmds.ls(type=renderer_node)
|
||||
result = settings[0] if settings else cmds.createNode(renderer_node)
|
||||
cmds.setAttr(arg.format(result), value)
|
||||
return result
|
||||
|
||||
def _set_vray_settings(self, asset):
|
||||
# type: (dict) -> None
|
||||
"""Sets important settings for Vray."""
|
||||
node = self._set_renderer_option(
|
||||
"VRaySettingsNode", "{}.fileNameRenderElementSeparator", "_"
|
||||
)
|
||||
settings = cmds.ls(type="VRaySettingsNode")
|
||||
node = settings[0] if settings else cmds.createNode("VRaySettingsNode")
|
||||
|
||||
# set separator
|
||||
# set it in vray menu
|
||||
if cmds.optionMenuGrp("vrayRenderElementSeparator", exists=True,
|
||||
q=True):
|
||||
items = cmds.optionMenuGrp(
|
||||
"vrayRenderElementSeparator", ill=True, query=True)
|
||||
|
||||
separators = [cmds.menuItem(i, label=True, query=True) for i in items] # noqa: E501
|
||||
try:
|
||||
sep_idx = separators.index(self.aov_separator)
|
||||
except ValueError:
|
||||
raise CreatorError(
|
||||
"AOV character {} not in {}".format(
|
||||
self.aov_separator, separators))
|
||||
|
||||
cmds.optionMenuGrp(
|
||||
"vrayRenderElementSeparator", sl=sep_idx + 1, edit=True)
|
||||
cmds.setAttr(
|
||||
"{}.fileNameRenderElementSeparator".format(node),
|
||||
self.aov_separator,
|
||||
type="string"
|
||||
)
|
||||
# set format to exr
|
||||
cmds.setAttr(
|
||||
"{}.imageFormatStr".format(node), 5)
|
||||
"{}.imageFormatStr".format(node), "exr", type="string")
|
||||
|
||||
# animType
|
||||
cmds.setAttr(
|
||||
|
|
|
|||