OP-2019 - merge develop

This commit is contained in:
Petr Kalis 2021-12-14 15:13:34 +01:00
commit c0bacd5fc6
601 changed files with 19614 additions and 6905 deletions

16
.github/pull_request_template.md vendored Normal file
View 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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
"""Definition of Igniter version."""
__version__ = "1.0.1"
__version__ = "1.0.2"

View file

@ -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,

View file

@ -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)

View file

@ -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):

View file

@ -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

View file

@ -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_()

View file

@ -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_()

View file

@ -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")

View file

@ -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))

View file

@ -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)

View file

@ -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>&lt;name&gt;</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>&lt;segment name&gt;</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>

View file

@ -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>&lt;name&gt;</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>&lt;segment name&gt;</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>&lt;segment name&gt;_&lt;video codec&gt;</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>

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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())

View file

@ -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
}
]
}
]

View file

@ -27,6 +27,7 @@ def _sync_utility_scripts(env=None):
fsd_paths = [os.path.join(
HOST_DIR,
"api",
"utility_scripts"
)]

View file

@ -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

View file

@ -1,6 +1,6 @@
import sys
from avalon.vendor.Qt import QtGui
from Qt import QtGui
import avalon.fusion
from avalon import io

View file

@ -1,5 +1,5 @@
from avalon import api, style
from avalon.vendor.Qt import QtGui, QtWidgets
from Qt import QtGui, QtWidgets
import avalon.fusion

View file

@ -1,4 +1,4 @@
from avalon.vendor.Qt import QtWidgets
from Qt import QtWidgets
from avalon.vendor import qtawesome
import avalon.fusion as avalon

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -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",

View file

@ -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

View file

@ -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)
)

View file

@ -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

View file

@ -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):

View file

@ -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)

View file

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 8 KiB

After

Width:  |  Height:  |  Size: 8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Before After
Before After

View file

@ -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 = []

View file

@ -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):

View file

@ -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
)

View file

@ -0,0 +1,7 @@
from .OTIOExportTask import OTIOExportTask
from .OTIOExportUI import OTIOExportUI
__all__ = [
"OTIOExportTask",
"OTIOExportUI"
]

View file

@ -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])

View file

@ -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)

View file

@ -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":

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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'))
#----------------------------------------------

View file

@ -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)

View file

@ -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

View file

@ -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:

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -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."""

View file

@ -1,7 +0,0 @@
from OTIOExportTask import OTIOExportTask
from OTIOExportUI import OTIOExportUI
__all__ = [
'OTIOExportTask',
'OTIOExportUI'
]

View file

@ -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)

View file

@ -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()

View file

@ -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>

View file

@ -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(

View file

@ -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

View file

@ -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):

View file

@ -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(

Some files were not shown because too many files have changed in this diff Show more