Merge branch 'release/3.15.x' into enhancement/tray_publisher_is_not_experimental
2
.github/workflows/milestone_assign.yml
vendored
|
|
@ -2,7 +2,7 @@ name: Milestone - assign to PRs
|
|||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, reopened, edited, synchronize]
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
run_if_release:
|
||||
|
|
|
|||
2
.github/workflows/prerelease.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.7
|
||||
python-version: 3.9
|
||||
|
||||
- name: Install Python requirements
|
||||
run: pip install gitpython semver PyGithub
|
||||
|
|
|
|||
2
.github/workflows/release.yml
vendored
|
|
@ -19,7 +19,7 @@ jobs:
|
|||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.7
|
||||
python-version: 3.9
|
||||
- name: Install Python requirements
|
||||
run: pip install gitpython semver PyGithub
|
||||
|
||||
|
|
|
|||
8
.github/workflows/test_build.yml
vendored
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.7]
|
||||
python-version: [3.9]
|
||||
|
||||
steps:
|
||||
- name: 🚛 Checkout Code
|
||||
|
|
@ -45,7 +45,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.7]
|
||||
python-version: [3.9]
|
||||
|
||||
steps:
|
||||
- name: 🚛 Checkout Code
|
||||
|
|
@ -70,7 +70,7 @@ jobs:
|
|||
# runs-on: macos-latest
|
||||
# strategy:
|
||||
# matrix:
|
||||
# python-version: [3.7]
|
||||
# python-version: [3.9]
|
||||
|
||||
# steps:
|
||||
# - name: 🚛 Checkout Code
|
||||
|
|
@ -87,4 +87,4 @@ jobs:
|
|||
|
||||
# - name: 🔨 Build
|
||||
# run: |
|
||||
# ./tools/build.sh
|
||||
# ./tools/build.sh
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Build Pype docker image
|
||||
FROM ubuntu:focal AS builder
|
||||
ARG OPENPYPE_PYTHON_VERSION=3.7.12
|
||||
ARG OPENPYPE_PYTHON_VERSION=3.9.12
|
||||
ARG BUILD_DATE
|
||||
ARG VERSION
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Build Pype docker image
|
||||
FROM centos:7 AS builder
|
||||
ARG OPENPYPE_PYTHON_VERSION=3.7.12
|
||||
ARG OPENPYPE_PYTHON_VERSION=3.9.12
|
||||
|
||||
LABEL org.opencontainers.image.name="pypeclub/openpype"
|
||||
LABEL org.opencontainers.image.title="OpenPype Docker Image"
|
||||
|
|
@ -96,11 +96,11 @@ RUN source $HOME/.bashrc \
|
|||
RUN source $HOME/.bashrc \
|
||||
&& bash ./tools/build.sh
|
||||
|
||||
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 /usr/lib64/libxcb* ./build/exe.linux-x86_64-3.7/vendor/python/PySide2/Qt/lib
|
||||
RUN cp /usr/lib64/libffi* ./build/exe.linux-x86_64-3.9/lib \
|
||||
&& cp /usr/lib64/libssl* ./build/exe.linux-x86_64-3.9/lib \
|
||||
&& cp /usr/lib64/libcrypto* ./build/exe.linux-x86_64-3.9/lib \
|
||||
&& cp /root/.pyenv/versions/${OPENPYPE_PYTHON_VERSION}/lib/libpython* ./build/exe.linux-x86_64-3.9/lib \
|
||||
&& cp /usr/lib64/libxcb* ./build/exe.linux-x86_64-3.9/vendor/python/PySide2/Qt/lib
|
||||
|
||||
RUN cd /opt/openpype \
|
||||
rm -rf ./vendor/bin
|
||||
|
|
|
|||
31
README.md
|
|
@ -5,7 +5,7 @@
|
|||
OpenPype
|
||||
====
|
||||
|
||||
[](https://github.com/pypeclub/pype/actions/workflows/documentation.yml) 
|
||||
[](https://github.com/pypeclub/pype/actions/workflows/documentation.yml) 
|
||||
|
||||
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ The main things you will need to run and build OpenPype are:
|
|||
- **Terminal** in your OS
|
||||
- PowerShell 5.0+ (Windows)
|
||||
- Bash (Linux)
|
||||
- [**Python 3.7.8**](#python) or higher
|
||||
- [**Python 3.9.6**](#python) or higher
|
||||
- [**MongoDB**](#database) (needed only for local development)
|
||||
|
||||
|
||||
|
|
@ -50,13 +50,14 @@ For more details on requirements visit [requirements documentation](https://open
|
|||
Building OpenPype
|
||||
-------------
|
||||
|
||||
To build OpenPype you currently need [Python 3.7](https://www.python.org/downloads/) as we are following
|
||||
To build OpenPype you currently need [Python 3.9](https://www.python.org/downloads/) as we are following
|
||||
[vfx platform](https://vfxplatform.com). Because of some Linux distros comes with newer Python version
|
||||
already, you need to install **3.7** version and make use of it. You can use perhaps [pyenv](https://github.com/pyenv/pyenv) for this on Linux.
|
||||
already, you need to install **3.9** version and make use of it. You can use perhaps [pyenv](https://github.com/pyenv/pyenv) for this on Linux.
|
||||
**Note**: We do not support 3.9.0 because of [this bug](https://github.com/python/cpython/pull/22670). Please, use higher versions of 3.9.x.
|
||||
|
||||
### Windows
|
||||
|
||||
You will need [Python 3.7](https://www.python.org/downloads/) and [git](https://git-scm.com/downloads).
|
||||
You will need [Python >= 3.9.1](https://www.python.org/downloads/) and [git](https://git-scm.com/downloads).
|
||||
More tools might be needed for installing dependencies (for example for **OpenTimelineIO**) - mostly
|
||||
development tools like [CMake](https://cmake.org/) and [Visual Studio](https://visualstudio.microsoft.com/cs/downloads/)
|
||||
|
||||
|
|
@ -82,7 +83,7 @@ OpenPype is build using [CX_Freeze](https://cx-freeze.readthedocs.io/en/latest)
|
|||
|
||||
### macOS
|
||||
|
||||
You will need [Python 3.7](https://www.python.org/downloads/) and [git](https://git-scm.com/downloads). You'll need also other tools to build
|
||||
You will need [Python >= 3.9](https://www.python.org/downloads/) and [git](https://git-scm.com/downloads). You'll need also other tools to build
|
||||
some OpenPype dependencies like [CMake](https://cmake.org/) and **XCode Command Line Tools** (or some other build system).
|
||||
|
||||
Easy way of installing everything necessary is to use [Homebrew](https://brew.sh):
|
||||
|
|
@ -106,19 +107,19 @@ exec "$SHELL"
|
|||
PATH=$(pyenv root)/shims:$PATH
|
||||
```
|
||||
|
||||
4) Pull in required Python version 3.7.x
|
||||
4) Pull in required Python version 3.9.x
|
||||
```sh
|
||||
# install Python build dependences
|
||||
brew install openssl readline sqlite3 xz zlib
|
||||
|
||||
# replace with up-to-date 3.7.x version
|
||||
pyenv install 3.7.9
|
||||
# replace with up-to-date 3.9.x version
|
||||
pyenv install 3.9.6
|
||||
```
|
||||
|
||||
5) Set local Python version
|
||||
```sh
|
||||
# switch to OpenPype source directory
|
||||
pyenv local 3.7.9
|
||||
pyenv local 3.9.6
|
||||
```
|
||||
|
||||
#### To build OpenPype:
|
||||
|
|
@ -145,7 +146,7 @@ sudo ./tools/docker_build.sh centos7
|
|||
If all is successful, you'll find built OpenPype in `./build/` folder.
|
||||
|
||||
#### Manual build
|
||||
You will need [Python 3.7](https://www.python.org/downloads/) and [git](https://git-scm.com/downloads). You'll also need [curl](https://curl.se) on systems that doesn't have one preinstalled.
|
||||
You will need [Python >= 3.9](https://www.python.org/downloads/) and [git](https://git-scm.com/downloads). You'll also need [curl](https://curl.se) on systems that doesn't have one preinstalled.
|
||||
|
||||
To build Python related stuff, you need Python header files installed (`python3-dev` on Ubuntu for example).
|
||||
|
||||
|
|
@ -222,14 +223,14 @@ eval "$(pyenv virtualenv-init -)"
|
|||
# reload shell
|
||||
exec $SHELL
|
||||
|
||||
# install Python 3.7.9
|
||||
pyenv install -v 3.7.9
|
||||
# install Python 3.9.x
|
||||
pyenv install -v 3.9.6
|
||||
|
||||
# change path to OpenPype 3
|
||||
cd /path/to/openpype-3
|
||||
|
||||
# set local python version
|
||||
pyenv local 3.7.9
|
||||
pyenv local 3.9.6
|
||||
|
||||
```
|
||||
</details>
|
||||
|
|
@ -345,4 +346,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
|
|
|||
|
|
@ -804,6 +804,8 @@ class BootstrapRepos:
|
|||
"""
|
||||
version = OpenPypeVersion.version_in_str(zip_file.name)
|
||||
destination_dir = self.data_dir / f"{version.major}.{version.minor}"
|
||||
if not destination_dir.exists():
|
||||
destination_dir.mkdir(parents=True)
|
||||
destination = destination_dir / zip_file.name
|
||||
|
||||
if destination.exists():
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ class InstallThread(QThread):
|
|||
|
||||
def __init__(self, parent=None,):
|
||||
self._mongo = None
|
||||
self._path = None
|
||||
self._result = None
|
||||
|
||||
QThread.__init__(self, parent)
|
||||
|
|
@ -62,143 +61,117 @@ class InstallThread(QThread):
|
|||
progress_callback=self.set_progress, message=self.message)
|
||||
local_version = OpenPypeVersion.get_installed_version_str()
|
||||
|
||||
# if user did enter nothing, we install OpenPype from local version.
|
||||
# zip content of `repos`, copy it to user data dir and append
|
||||
# version to it.
|
||||
if not self._path:
|
||||
# user did not entered url
|
||||
if not self._mongo:
|
||||
# it not set in environment
|
||||
if not os.getenv("OPENPYPE_MONGO"):
|
||||
# try to get it from settings registry
|
||||
try:
|
||||
self._mongo = bs.secure_registry.get_item(
|
||||
"openPypeMongo")
|
||||
except ValueError:
|
||||
self.message.emit(
|
||||
"!!! We need MongoDB URL to proceed.", True)
|
||||
self._set_result(-1)
|
||||
return
|
||||
else:
|
||||
self._mongo = os.getenv("OPENPYPE_MONGO")
|
||||
else:
|
||||
self.message.emit("Saving mongo connection string ...", False)
|
||||
bs.secure_registry.set_item("openPypeMongo", self._mongo)
|
||||
|
||||
os.environ["OPENPYPE_MONGO"] = self._mongo
|
||||
|
||||
self.message.emit(
|
||||
f"Detecting installed OpenPype versions in {bs.data_dir}",
|
||||
False)
|
||||
detected = bs.find_openpype(include_zips=True)
|
||||
|
||||
if detected:
|
||||
if not OpenPypeVersion.get_installed_version().is_compatible(
|
||||
detected[-1]):
|
||||
self.message.emit((
|
||||
f"Latest detected version {detected[-1]} "
|
||||
"is not compatible with the currently running "
|
||||
f"{local_version}"
|
||||
), True)
|
||||
self.message.emit((
|
||||
"Filtering detected versions to compatible ones..."
|
||||
), False)
|
||||
|
||||
detected = [
|
||||
version for version in detected
|
||||
if version.is_compatible(
|
||||
OpenPypeVersion.get_installed_version())
|
||||
]
|
||||
|
||||
if OpenPypeVersion(
|
||||
version=local_version, path=Path()) < detected[-1]:
|
||||
self.message.emit((
|
||||
f"Latest installed version {detected[-1]} is newer "
|
||||
f"then currently running {local_version}"
|
||||
), False)
|
||||
self.message.emit("Skipping OpenPype install ...", False)
|
||||
if detected[-1].path.suffix.lower() == ".zip":
|
||||
bs.extract_openpype(detected[-1])
|
||||
self._set_result(0)
|
||||
return
|
||||
|
||||
if OpenPypeVersion(version=local_version).get_main_version() == detected[-1].get_main_version(): # noqa
|
||||
self.message.emit((
|
||||
f"Latest installed version is the same as "
|
||||
f"currently running {local_version}"
|
||||
), False)
|
||||
self.message.emit("Skipping OpenPype install ...", False)
|
||||
self._set_result(0)
|
||||
return
|
||||
|
||||
self.message.emit((
|
||||
"All installed versions are older then "
|
||||
f"currently running one {local_version}"
|
||||
), False)
|
||||
else:
|
||||
if getattr(sys, 'frozen', False):
|
||||
self.message.emit("None detected.", True)
|
||||
self.message.emit(("We will use OpenPype coming with "
|
||||
"installer."), False)
|
||||
openpype_version = bs.create_version_from_frozen_code()
|
||||
if not openpype_version:
|
||||
self.message.emit(
|
||||
f"!!! Install failed - {openpype_version}", True)
|
||||
self._set_result(-1)
|
||||
return
|
||||
self.message.emit(f"Using: {openpype_version}", False)
|
||||
bs.install_version(openpype_version)
|
||||
self.message.emit(f"Installed as {openpype_version}", False)
|
||||
self.progress.emit(100)
|
||||
self._set_result(1)
|
||||
return
|
||||
else:
|
||||
self.message.emit("None detected.", False)
|
||||
|
||||
self.message.emit(
|
||||
f"We will use local OpenPype version {local_version}", False)
|
||||
|
||||
local_openpype = bs.create_version_from_live_code()
|
||||
if not local_openpype:
|
||||
self.message.emit(
|
||||
f"!!! Install failed - {local_openpype}", True)
|
||||
self._set_result(-1)
|
||||
return
|
||||
# user did not entered url
|
||||
if self._mongo:
|
||||
self.message.emit("Saving mongo connection string ...", False)
|
||||
bs.secure_registry.set_item("openPypeMongo", self._mongo)
|
||||
|
||||
elif os.getenv("OPENPYPE_MONGO"):
|
||||
self._mongo = os.getenv("OPENPYPE_MONGO")
|
||||
else:
|
||||
# try to get it from settings registry
|
||||
try:
|
||||
bs.install_version(local_openpype)
|
||||
except (OpenPypeVersionExists,
|
||||
OpenPypeVersionInvalid,
|
||||
OpenPypeVersionIOError) as e:
|
||||
self.message.emit(f"Installed failed: ", True)
|
||||
self.message.emit(str(e), True)
|
||||
self._mongo = bs.secure_registry.get_item(
|
||||
"openPypeMongo")
|
||||
except ValueError:
|
||||
self.message.emit(
|
||||
"!!! We need MongoDB URL to proceed.", True)
|
||||
self._set_result(-1)
|
||||
return
|
||||
os.environ["OPENPYPE_MONGO"] = self._mongo
|
||||
|
||||
self.message.emit(f"Installed as {local_openpype}", False)
|
||||
self.message.emit(
|
||||
f"Detecting installed OpenPype versions in {bs.data_dir}",
|
||||
False)
|
||||
detected = bs.find_openpype(include_zips=True)
|
||||
if not detected and getattr(sys, 'frozen', False):
|
||||
self.message.emit("None detected.", True)
|
||||
self.message.emit(("We will use OpenPype coming with "
|
||||
"installer."), False)
|
||||
openpype_version = bs.create_version_from_frozen_code()
|
||||
if not openpype_version:
|
||||
self.message.emit(
|
||||
f"!!! Install failed - {openpype_version}", True)
|
||||
self._set_result(-1)
|
||||
return
|
||||
self.message.emit(f"Using: {openpype_version}", False)
|
||||
bs.install_version(openpype_version)
|
||||
self.message.emit(f"Installed as {openpype_version}", False)
|
||||
self.progress.emit(100)
|
||||
self._set_result(1)
|
||||
return
|
||||
else:
|
||||
# if we have mongo connection string, validate it, set it to
|
||||
# user settings and get OPENPYPE_PATH from there.
|
||||
if self._mongo:
|
||||
if not validate_mongo_connection(self._mongo):
|
||||
self.message.emit(
|
||||
f"!!! invalid mongo url {self._mongo}", True)
|
||||
self._set_result(-1)
|
||||
return
|
||||
bs.secure_registry.set_item("openPypeMongo", self._mongo)
|
||||
os.environ["OPENPYPE_MONGO"] = self._mongo
|
||||
|
||||
self.message.emit(f"processing {self._path}", True)
|
||||
repo_file = bs.process_entered_location(self._path)
|
||||
if detected and not OpenPypeVersion.get_installed_version().is_compatible(detected[-1]): # noqa: E501
|
||||
self.message.emit((
|
||||
f"Latest detected version {detected[-1]} "
|
||||
"is not compatible with the currently running "
|
||||
f"{local_version}"
|
||||
), True)
|
||||
self.message.emit((
|
||||
"Filtering detected versions to compatible ones..."
|
||||
), False)
|
||||
|
||||
if not repo_file:
|
||||
self.message.emit("!!! Cannot install", True)
|
||||
self._set_result(-1)
|
||||
# filter results to get only compatible versions
|
||||
detected = [
|
||||
version for version in detected
|
||||
if version.is_compatible(
|
||||
OpenPypeVersion.get_installed_version())
|
||||
]
|
||||
|
||||
if detected:
|
||||
if OpenPypeVersion(
|
||||
version=local_version, path=Path()) < detected[-1]:
|
||||
self.message.emit((
|
||||
f"Latest installed version {detected[-1]} is newer "
|
||||
f"then currently running {local_version}"
|
||||
), False)
|
||||
self.message.emit("Skipping OpenPype install ...", False)
|
||||
if detected[-1].path.suffix.lower() == ".zip":
|
||||
bs.extract_openpype(detected[-1])
|
||||
self._set_result(0)
|
||||
return
|
||||
|
||||
if OpenPypeVersion(version=local_version).get_main_version() == detected[-1].get_main_version(): # noqa: E501
|
||||
self.message.emit((
|
||||
f"Latest installed version is the same as "
|
||||
f"currently running {local_version}"
|
||||
), False)
|
||||
self.message.emit("Skipping OpenPype install ...", False)
|
||||
self._set_result(0)
|
||||
return
|
||||
|
||||
self.message.emit((
|
||||
"All installed versions are older then "
|
||||
f"currently running one {local_version}"
|
||||
), False)
|
||||
|
||||
self.message.emit("None detected.", False)
|
||||
|
||||
self.message.emit(
|
||||
f"We will use local OpenPype version {local_version}", False)
|
||||
|
||||
local_openpype = bs.create_version_from_live_code()
|
||||
if not local_openpype:
|
||||
self.message.emit(
|
||||
f"!!! Install failed - {local_openpype}", True)
|
||||
self._set_result(-1)
|
||||
return
|
||||
|
||||
try:
|
||||
bs.install_version(local_openpype)
|
||||
except (OpenPypeVersionExists,
|
||||
OpenPypeVersionInvalid,
|
||||
OpenPypeVersionIOError) as e:
|
||||
self.message.emit(f"Installed failed: ", True)
|
||||
self.message.emit(str(e), True)
|
||||
self._set_result(-1)
|
||||
return
|
||||
|
||||
self.message.emit(f"Installed as {local_openpype}", False)
|
||||
self.progress.emit(100)
|
||||
self._set_result(1)
|
||||
return
|
||||
|
||||
self.progress.emit(100)
|
||||
self._set_result(1)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@ Source: "build\{#build}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdir
|
|||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
[Icons]
|
||||
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\openpype_gui.exe"
|
||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\openpype_gui.exe"; Tasks: desktopicon
|
||||
Name: "{autoprograms}\{#MyAppName} {#AppVer}"; Filename: "{app}\openpype_gui.exe"
|
||||
Name: "{autodesktop}\{#MyAppName} {#AppVer}"; Filename: "{app}\openpype_gui.exe"; Tasks: desktopicon
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\openpype_gui.exe"; Description: "{cm:LaunchProgram,OpenPype}"; Flags: nowait postinstall skipifsilent
|
||||
|
|
|
|||
|
|
@ -10,30 +10,15 @@ from .launch_logic import (
|
|||
)
|
||||
|
||||
from .pipeline import (
|
||||
AfterEffectsHost,
|
||||
ls,
|
||||
get_asset_settings,
|
||||
install,
|
||||
uninstall,
|
||||
list_instances,
|
||||
remove_instance,
|
||||
containerise,
|
||||
get_context_data,
|
||||
update_context_data,
|
||||
get_context_title
|
||||
)
|
||||
|
||||
from .workio import (
|
||||
file_extensions,
|
||||
has_unsaved_changes,
|
||||
save_file,
|
||||
open_file,
|
||||
current_file,
|
||||
work_root,
|
||||
containerise
|
||||
)
|
||||
|
||||
from .lib import (
|
||||
maintained_selection,
|
||||
get_extension_manifest_path
|
||||
get_extension_manifest_path,
|
||||
get_asset_settings
|
||||
)
|
||||
|
||||
from .plugin import (
|
||||
|
|
@ -48,26 +33,12 @@ __all__ = [
|
|||
|
||||
# pipeline
|
||||
"ls",
|
||||
"get_asset_settings",
|
||||
"install",
|
||||
"uninstall",
|
||||
"list_instances",
|
||||
"remove_instance",
|
||||
"containerise",
|
||||
"get_context_data",
|
||||
"update_context_data",
|
||||
"get_context_title",
|
||||
|
||||
"file_extensions",
|
||||
"has_unsaved_changes",
|
||||
"save_file",
|
||||
"open_file",
|
||||
"current_file",
|
||||
"work_root",
|
||||
|
||||
# lib
|
||||
"maintained_selection",
|
||||
"get_extension_manifest_path",
|
||||
"get_asset_settings",
|
||||
|
||||
# plugin
|
||||
"AfterEffectsLoader"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ExtensionManifest Version="8.0" ExtensionBundleId="com.openpype.AE.panel" ExtensionBundleVersion="1.0.23"
|
||||
<ExtensionManifest Version="8.0" ExtensionBundleId="com.openpype.AE.panel" ExtensionBundleVersion="1.0.24"
|
||||
ExtensionBundleName="openpype" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<ExtensionList>
|
||||
<Extension Id="com.openpype.AE.panel" Version="1.0" />
|
||||
|
|
|
|||
|
|
@ -38,17 +38,6 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#creator-button").bind("click", function() {
|
||||
RPC.call('AfterEffects.creator_route').then(function (data) {
|
||||
}, function (error) {
|
||||
alert(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#loader-button").bind("click", function() {
|
||||
|
|
@ -82,17 +71,6 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#subsetmanager-button").bind("click", function() {
|
||||
RPC.call('AfterEffects.subsetmanager_route').then(function (data) {
|
||||
}, function (error) {
|
||||
alert(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#experimental-button").bind("click", function() {
|
||||
|
|
@ -113,11 +91,9 @@
|
|||
|
||||
<div>
|
||||
<div></div><a href=# id=workfiles-button><button class="hostFontSize">Workfiles...</button></a></div>
|
||||
<div> <a href=# id=creator-button><button class="hostFontSize">Create...</button></a></div>
|
||||
<div><a href=# id=loader-button><button class="hostFontSize">Load...</button></a></div>
|
||||
<div><a href=# id=publish-button><button class="hostFontSize">Publish...</button></a></div>
|
||||
<div><a href=# id=sceneinventory-button><button class="hostFontSize">Manage...</button></a></div>
|
||||
<div><a href=# id=subsetmanager-button><button class="hostFontSize">Subset Manager...</button></a></div>
|
||||
<div><a href=# id=experimental-button><button class="hostFontSize">Experimental Tools...</button></a></div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -284,9 +284,6 @@ class AfterEffectsRoute(WebSocketRoute):
|
|||
return await self.socket.call('aftereffects.read')
|
||||
|
||||
# panel routes for tools
|
||||
async def creator_route(self):
|
||||
self._tool_route("creator")
|
||||
|
||||
async def workfiles_route(self):
|
||||
self._tool_route("workfiles")
|
||||
|
||||
|
|
@ -294,14 +291,11 @@ class AfterEffectsRoute(WebSocketRoute):
|
|||
self._tool_route("loader")
|
||||
|
||||
async def publish_route(self):
|
||||
self._tool_route("publish")
|
||||
self._tool_route("publisher")
|
||||
|
||||
async def sceneinventory_route(self):
|
||||
self._tool_route("sceneinventory")
|
||||
|
||||
async def subsetmanager_route(self):
|
||||
self._tool_route("subsetmanager")
|
||||
|
||||
async def experimental_tools_route(self):
|
||||
self._tool_route("experimental_tools")
|
||||
|
||||
|
|
|
|||
|
|
@ -26,9 +26,10 @@ def safe_excepthook(*args):
|
|||
def main(*subprocess_args):
|
||||
sys.excepthook = safe_excepthook
|
||||
|
||||
from openpype.hosts.aftereffects import api
|
||||
from openpype.hosts.aftereffects.api import AfterEffectsHost
|
||||
|
||||
install_host(api)
|
||||
host = AfterEffectsHost()
|
||||
install_host(host)
|
||||
|
||||
os.environ["OPENPYPE_LOG_NO_COLORS"] = "False"
|
||||
app = QtWidgets.QApplication([])
|
||||
|
|
@ -133,3 +134,32 @@ def get_background_layers(file_url):
|
|||
layer.get("filename")).
|
||||
replace("\\", "/"))
|
||||
return layers
|
||||
|
||||
|
||||
def get_asset_settings(asset_doc):
|
||||
"""Get settings on current asset from database.
|
||||
|
||||
Returns:
|
||||
dict: Scene data.
|
||||
|
||||
"""
|
||||
asset_data = asset_doc["data"]
|
||||
fps = asset_data.get("fps")
|
||||
frame_start = asset_data.get("frameStart")
|
||||
frame_end = asset_data.get("frameEnd")
|
||||
handle_start = asset_data.get("handleStart")
|
||||
handle_end = asset_data.get("handleEnd")
|
||||
resolution_width = asset_data.get("resolutionWidth")
|
||||
resolution_height = asset_data.get("resolutionHeight")
|
||||
duration = (frame_end - frame_start + 1) + handle_start + handle_end
|
||||
|
||||
return {
|
||||
"fps": fps,
|
||||
"frameStart": frame_start,
|
||||
"frameEnd": frame_end,
|
||||
"handleStart": handle_start,
|
||||
"handleEnd": handle_end,
|
||||
"resolutionWidth": resolution_width,
|
||||
"resolutionHeight": resolution_height,
|
||||
"duration": duration
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import os
|
||||
|
||||
from Qt import QtWidgets
|
||||
|
||||
import pyblish.api
|
||||
|
|
@ -16,6 +15,13 @@ from openpype.pipeline import (
|
|||
from openpype.pipeline.load import any_outdated_containers
|
||||
import openpype.hosts.aftereffects
|
||||
|
||||
from openpype.host import (
|
||||
HostBase,
|
||||
IWorkfileHost,
|
||||
ILoadHost,
|
||||
IPublishHost
|
||||
)
|
||||
|
||||
from .launch_logic import get_stub, ConnectionNotEstablishedYet
|
||||
|
||||
log = Logger.get_logger(__name__)
|
||||
|
|
@ -30,27 +36,142 @@ LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
|
|||
CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
|
||||
|
||||
|
||||
def install():
|
||||
print("Installing Pype config...")
|
||||
class AfterEffectsHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
||||
name = "aftereffects"
|
||||
|
||||
pyblish.api.register_host("aftereffects")
|
||||
pyblish.api.register_plugin_path(PUBLISH_PATH)
|
||||
def __init__(self):
|
||||
self._stub = None
|
||||
super(AfterEffectsHost, self).__init__()
|
||||
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
register_creator_plugin_path(CREATE_PATH)
|
||||
log.info(PUBLISH_PATH)
|
||||
@property
|
||||
def stub(self):
|
||||
"""
|
||||
Handle pulling stub from PS to run operations on host
|
||||
Returns:
|
||||
(AEServerStub) or None
|
||||
"""
|
||||
if self._stub:
|
||||
return self._stub
|
||||
|
||||
pyblish.api.register_callback(
|
||||
"instanceToggled", on_pyblish_instance_toggled
|
||||
)
|
||||
try:
|
||||
stub = get_stub() # only after Photoshop is up
|
||||
except ConnectionNotEstablishedYet:
|
||||
print("Not connected yet, ignoring")
|
||||
return
|
||||
|
||||
register_event_callback("application.launched", application_launch)
|
||||
if not stub.get_active_document_name():
|
||||
return
|
||||
|
||||
self._stub = stub
|
||||
return self._stub
|
||||
|
||||
def uninstall():
|
||||
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
|
||||
deregister_loader_plugin_path(LOAD_PATH)
|
||||
deregister_creator_plugin_path(CREATE_PATH)
|
||||
def install(self):
|
||||
print("Installing Pype config...")
|
||||
|
||||
pyblish.api.register_host("aftereffects")
|
||||
pyblish.api.register_plugin_path(PUBLISH_PATH)
|
||||
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
register_creator_plugin_path(CREATE_PATH)
|
||||
log.info(PUBLISH_PATH)
|
||||
|
||||
pyblish.api.register_callback(
|
||||
"instanceToggled", on_pyblish_instance_toggled
|
||||
)
|
||||
|
||||
register_event_callback("application.launched", application_launch)
|
||||
|
||||
def get_workfile_extensions(self):
|
||||
return [".aep"]
|
||||
|
||||
def save_workfile(self, dst_path=None):
|
||||
self.stub.saveAs(dst_path, True)
|
||||
|
||||
def open_workfile(self, filepath):
|
||||
self.stub.open(filepath)
|
||||
|
||||
return True
|
||||
|
||||
def get_current_workfile(self):
|
||||
try:
|
||||
full_name = get_stub().get_active_document_full_name()
|
||||
if full_name and full_name != "null":
|
||||
return os.path.normpath(full_name).replace("\\", "/")
|
||||
except ValueError:
|
||||
print("Nothing opened")
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
def get_containers(self):
|
||||
return ls()
|
||||
|
||||
def get_context_data(self):
|
||||
meta = self.stub.get_metadata()
|
||||
for item in meta:
|
||||
if item.get("id") == "publish_context":
|
||||
item.pop("id")
|
||||
return item
|
||||
|
||||
return {}
|
||||
|
||||
def update_context_data(self, data, changes):
|
||||
item = data
|
||||
item["id"] = "publish_context"
|
||||
self.stub.imprint(item["id"], item)
|
||||
|
||||
# created instances section
|
||||
def list_instances(self):
|
||||
"""List all created instances from current workfile which
|
||||
will be published.
|
||||
|
||||
Pulls from File > File Info
|
||||
|
||||
For SubsetManager
|
||||
|
||||
Returns:
|
||||
(list) of dictionaries matching instances format
|
||||
"""
|
||||
stub = self.stub
|
||||
if not stub:
|
||||
return []
|
||||
|
||||
instances = []
|
||||
layers_meta = stub.get_metadata()
|
||||
|
||||
for instance in layers_meta:
|
||||
if instance.get("id") == "pyblish.avalon.instance":
|
||||
instances.append(instance)
|
||||
return instances
|
||||
|
||||
def remove_instance(self, instance):
|
||||
"""Remove instance from current workfile metadata.
|
||||
|
||||
Updates metadata of current file in File > File Info and removes
|
||||
icon highlight on group layer.
|
||||
|
||||
For SubsetManager
|
||||
|
||||
Args:
|
||||
instance (dict): instance representation from subsetmanager model
|
||||
"""
|
||||
stub = self.stub
|
||||
|
||||
if not stub:
|
||||
return
|
||||
|
||||
inst_id = instance.get("instance_id") or instance.get("uuid") # legacy
|
||||
if not inst_id:
|
||||
log.warning("No instance identifier for {}".format(instance))
|
||||
return
|
||||
|
||||
stub.remove_instance(inst_id)
|
||||
|
||||
if instance.get("members"):
|
||||
item = stub.get_item(instance["members"][0])
|
||||
if item:
|
||||
stub.rename_item(item.id,
|
||||
item.name.replace(stub.PUBLISH_ICON, ''))
|
||||
|
||||
|
||||
def application_launch():
|
||||
|
|
@ -63,35 +184,6 @@ def on_pyblish_instance_toggled(instance, old_value, new_value):
|
|||
instance[0].Visible = new_value
|
||||
|
||||
|
||||
def get_asset_settings(asset_doc):
|
||||
"""Get settings on current asset from database.
|
||||
|
||||
Returns:
|
||||
dict: Scene data.
|
||||
|
||||
"""
|
||||
asset_data = asset_doc["data"]
|
||||
fps = asset_data.get("fps")
|
||||
frame_start = asset_data.get("frameStart")
|
||||
frame_end = asset_data.get("frameEnd")
|
||||
handle_start = asset_data.get("handleStart")
|
||||
handle_end = asset_data.get("handleEnd")
|
||||
resolution_width = asset_data.get("resolutionWidth")
|
||||
resolution_height = asset_data.get("resolutionHeight")
|
||||
duration = (frame_end - frame_start + 1) + handle_start + handle_end
|
||||
|
||||
return {
|
||||
"fps": fps,
|
||||
"frameStart": frame_start,
|
||||
"frameEnd": frame_end,
|
||||
"handleStart": handle_start,
|
||||
"handleEnd": handle_end,
|
||||
"resolutionWidth": resolution_width,
|
||||
"resolutionHeight": resolution_height,
|
||||
"duration": duration
|
||||
}
|
||||
|
||||
|
||||
def ls():
|
||||
"""Yields containers from active AfterEffects document.
|
||||
|
||||
|
|
@ -191,102 +283,17 @@ def containerise(name,
|
|||
return comp
|
||||
|
||||
|
||||
# created instances section
|
||||
def list_instances():
|
||||
"""
|
||||
List all created instances from current workfile which
|
||||
will be published.
|
||||
def cache_and_get_instances(creator):
|
||||
"""Cache instances in shared data.
|
||||
|
||||
Pulls from File > File Info
|
||||
|
||||
For SubsetManager
|
||||
|
||||
Returns:
|
||||
(list) of dictionaries matching instances format
|
||||
"""
|
||||
stub = _get_stub()
|
||||
if not stub:
|
||||
return []
|
||||
|
||||
instances = []
|
||||
layers_meta = stub.get_metadata()
|
||||
|
||||
for instance in layers_meta:
|
||||
if instance.get("id") == "pyblish.avalon.instance":
|
||||
instances.append(instance)
|
||||
return instances
|
||||
|
||||
|
||||
def remove_instance(instance):
|
||||
"""
|
||||
Remove instance from current workfile metadata.
|
||||
|
||||
Updates metadata of current file in File > File Info and removes
|
||||
icon highlight on group layer.
|
||||
|
||||
For SubsetManager
|
||||
|
||||
Args:
|
||||
instance (dict): instance representation from subsetmanager model
|
||||
"""
|
||||
stub = _get_stub()
|
||||
|
||||
if not stub:
|
||||
return
|
||||
|
||||
inst_id = instance.get("instance_id") or instance.get("uuid") # legacy
|
||||
if not inst_id:
|
||||
log.warning("No instance identifier for {}".format(instance))
|
||||
return
|
||||
|
||||
stub.remove_instance(inst_id)
|
||||
|
||||
if instance.get("members"):
|
||||
item = stub.get_item(instance["members"][0])
|
||||
if item:
|
||||
stub.rename_item(item.id,
|
||||
item.name.replace(stub.PUBLISH_ICON, ''))
|
||||
|
||||
|
||||
# new publisher section
|
||||
def get_context_data():
|
||||
meta = _get_stub().get_metadata()
|
||||
for item in meta:
|
||||
if item.get("id") == "publish_context":
|
||||
item.pop("id")
|
||||
return item
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
def update_context_data(data, changes):
|
||||
item = data
|
||||
item["id"] = "publish_context"
|
||||
_get_stub().imprint(item["id"], item)
|
||||
|
||||
|
||||
def get_context_title():
|
||||
"""Returns title for Creator window"""
|
||||
|
||||
project_name = legacy_io.Session["AVALON_PROJECT"]
|
||||
asset_name = legacy_io.Session["AVALON_ASSET"]
|
||||
task_name = legacy_io.Session["AVALON_TASK"]
|
||||
return "{}/{}/{}".format(project_name, asset_name, task_name)
|
||||
|
||||
|
||||
def _get_stub():
|
||||
"""
|
||||
Handle pulling stub from PS to run operations on host
|
||||
Storing all instances as a list as legacy instances might be still present.
|
||||
Args:
|
||||
creator (Creator): Plugin which would like to get instances from host.
|
||||
Returns:
|
||||
(AEServerStub) or None
|
||||
List[]: list of all instances stored in metadata
|
||||
"""
|
||||
try:
|
||||
stub = get_stub() # only after Photoshop is up
|
||||
except ConnectionNotEstablishedYet:
|
||||
print("Not connected yet, ignoring")
|
||||
return
|
||||
|
||||
if not stub.get_active_document_name():
|
||||
return
|
||||
|
||||
return stub
|
||||
shared_key = "openpype.photoshop.instances"
|
||||
if shared_key not in creator.collection_shared_data:
|
||||
creator.collection_shared_data[shared_key] = \
|
||||
creator.host.list_instances()
|
||||
return creator.collection_shared_data[shared_key]
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
"""Host API required Work Files tool"""
|
||||
import os
|
||||
|
||||
from .launch_logic import get_stub
|
||||
|
||||
|
||||
def file_extensions():
|
||||
return [".aep"]
|
||||
|
||||
|
||||
def has_unsaved_changes():
|
||||
if _active_document():
|
||||
return not get_stub().is_saved()
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def save_file(filepath):
|
||||
get_stub().saveAs(filepath, True)
|
||||
|
||||
|
||||
def open_file(filepath):
|
||||
get_stub().open(filepath)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def current_file():
|
||||
try:
|
||||
full_name = get_stub().get_active_document_full_name()
|
||||
if full_name and full_name != "null":
|
||||
return os.path.normpath(full_name).replace("\\", "/")
|
||||
except ValueError:
|
||||
print("Nothing opened")
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def work_root(session):
|
||||
return os.path.normpath(session["AVALON_WORKDIR"]).replace("\\", "/")
|
||||
|
||||
|
||||
def _active_document():
|
||||
# TODO merge with current_file - even in extension
|
||||
document_name = None
|
||||
try:
|
||||
document_name = get_stub().get_active_document_name()
|
||||
except ValueError:
|
||||
print("Nothing opened")
|
||||
pass
|
||||
|
||||
return document_name
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
from openpype.hosts.aftereffects.plugins.create import create_legacy_render
|
||||
|
||||
|
||||
class CreateLocalRender(create_legacy_render.CreateRender):
|
||||
""" Creator to render locally.
|
||||
|
||||
Created only after default render on farm. So family 'render.local' is
|
||||
used for backward compatibility.
|
||||
"""
|
||||
|
||||
name = "renderDefault"
|
||||
label = "Render Locally"
|
||||
family = "renderLocal"
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
from openpype.pipeline import create
|
||||
from openpype.pipeline import CreatorError
|
||||
from openpype.hosts.aftereffects.api import (
|
||||
get_stub,
|
||||
list_instances
|
||||
)
|
||||
|
||||
|
||||
class CreateRender(create.LegacyCreator):
|
||||
"""Render folder for publish.
|
||||
|
||||
Creates subsets in format 'familyTaskSubsetname',
|
||||
eg 'renderCompositingMain'.
|
||||
|
||||
Create only single instance from composition at a time.
|
||||
"""
|
||||
|
||||
name = "renderDefault"
|
||||
label = "Render on Farm"
|
||||
family = "render"
|
||||
defaults = ["Main"]
|
||||
|
||||
def process(self):
|
||||
stub = get_stub() # only after After Effects is up
|
||||
items = []
|
||||
if (self.options or {}).get("useSelection"):
|
||||
items = stub.get_selected_items(
|
||||
comps=True, folders=False, footages=False
|
||||
)
|
||||
if len(items) > 1:
|
||||
raise CreatorError(
|
||||
"Please select only single composition at time."
|
||||
)
|
||||
|
||||
if not items:
|
||||
raise CreatorError((
|
||||
"Nothing to create. Select composition "
|
||||
"if 'useSelection' or create at least "
|
||||
"one composition."
|
||||
))
|
||||
|
||||
existing_subsets = [
|
||||
instance['subset'].lower()
|
||||
for instance in list_instances()
|
||||
]
|
||||
|
||||
item = items.pop()
|
||||
if self.name.lower() in existing_subsets:
|
||||
txt = "Instance with name \"{}\" already exists.".format(self.name)
|
||||
raise CreatorError(txt)
|
||||
|
||||
self.data["members"] = [item.id]
|
||||
self.data["uuid"] = item.id # for SubsetManager
|
||||
self.data["subset"] = (
|
||||
self.data["subset"]
|
||||
.replace(stub.PUBLISH_ICON, '')
|
||||
.replace(stub.LOADED_ICON, '')
|
||||
)
|
||||
|
||||
stub.imprint(item, self.data)
|
||||
stub.set_label_color(item.id, 14) # Cyan options 0 - 16
|
||||
stub.rename_item(item.id, stub.PUBLISH_ICON + self.data["subset"])
|
||||
|
|
@ -7,6 +7,7 @@ from openpype.pipeline import (
|
|||
CreatorError,
|
||||
legacy_io,
|
||||
)
|
||||
from openpype.hosts.aftereffects.api.pipeline import cache_and_get_instances
|
||||
|
||||
|
||||
class RenderCreator(Creator):
|
||||
|
|
@ -28,7 +29,7 @@ class RenderCreator(Creator):
|
|||
return resources.get_openpype_splash_filepath()
|
||||
|
||||
def collect_instances(self):
|
||||
for instance_data in api.list_instances():
|
||||
for instance_data in cache_and_get_instances(self):
|
||||
# legacy instances have family=='render' or 'renderLocal', use them
|
||||
creator_id = (instance_data.get("creator_identifier") or
|
||||
instance_data.get("family", '').replace("Local", ''))
|
||||
|
|
@ -46,7 +47,7 @@ class RenderCreator(Creator):
|
|||
|
||||
def remove_instances(self, instances):
|
||||
for instance in instances:
|
||||
api.remove_instance(instance)
|
||||
self.host.remove_instance(instance)
|
||||
self._remove_instance_from_context(instance)
|
||||
|
||||
def create(self, subset_name, data, pre_create_data):
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from openpype.pipeline import (
|
|||
CreatedInstance,
|
||||
legacy_io,
|
||||
)
|
||||
from openpype.hosts.aftereffects.api.pipeline import cache_and_get_instances
|
||||
|
||||
|
||||
class AEWorkfileCreator(AutoCreator):
|
||||
|
|
@ -17,7 +18,7 @@ class AEWorkfileCreator(AutoCreator):
|
|||
return []
|
||||
|
||||
def collect_instances(self):
|
||||
for instance_data in api.list_instances():
|
||||
for instance_data in cache_and_get_instances(self):
|
||||
creator_id = instance_data.get("creator_identifier")
|
||||
if creator_id == self.identifier:
|
||||
subset_name = instance_data["subset"]
|
||||
|
|
@ -55,7 +56,7 @@ class AEWorkfileCreator(AutoCreator):
|
|||
}
|
||||
data.update(self.get_dynamic_data(
|
||||
self.default_variant, task_name, asset_doc,
|
||||
project_name, host_name
|
||||
project_name, host_name, None
|
||||
))
|
||||
|
||||
new_instance = CreatedInstance(
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Context of the given subset doesn't match your current scene.
|
|||
|
||||
### How to repair?
|
||||
|
||||
You can fix this with "repair" button on the right.
|
||||
You can fix this with "repair" button on the right and refresh Publish at the bottom right.
|
||||
</description>
|
||||
<detail>
|
||||
### __Detailed Info__ (optional)
|
||||
|
|
|
|||
|
|
@ -7,28 +7,15 @@ Anything that isn't defined here is INTERNAL and unreliable for external use.
|
|||
from .launch_logic import stub
|
||||
|
||||
from .pipeline import (
|
||||
PhotoshopHost,
|
||||
ls,
|
||||
list_instances,
|
||||
remove_instance,
|
||||
install,
|
||||
uninstall,
|
||||
containerise,
|
||||
get_context_data,
|
||||
update_context_data,
|
||||
get_context_title
|
||||
containerise
|
||||
)
|
||||
from .plugin import (
|
||||
PhotoshopLoader,
|
||||
get_unique_layer_name
|
||||
)
|
||||
from .workio import (
|
||||
file_extensions,
|
||||
has_unsaved_changes,
|
||||
save_file,
|
||||
open_file,
|
||||
current_file,
|
||||
work_root,
|
||||
)
|
||||
|
||||
|
||||
from .lib import (
|
||||
maintained_selection,
|
||||
|
|
@ -40,28 +27,14 @@ __all__ = [
|
|||
"stub",
|
||||
|
||||
# pipeline
|
||||
"PhotoshopHost",
|
||||
"ls",
|
||||
"list_instances",
|
||||
"remove_instance",
|
||||
"install",
|
||||
"uninstall",
|
||||
"containerise",
|
||||
"get_context_data",
|
||||
"update_context_data",
|
||||
"get_context_title",
|
||||
|
||||
# Plugin
|
||||
"PhotoshopLoader",
|
||||
"get_unique_layer_name",
|
||||
|
||||
# workfiles
|
||||
"file_extensions",
|
||||
"has_unsaved_changes",
|
||||
"save_file",
|
||||
"open_file",
|
||||
"current_file",
|
||||
"work_root",
|
||||
|
||||
# lib
|
||||
"maintained_selection",
|
||||
"maintained_visibility",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<ExtensionManifest ExtensionBundleId="com.openpype.PS.panel" ExtensionBundleVersion="1.0.11" Version="7.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<ExtensionManifest ExtensionBundleId="com.openpype.PS.panel" ExtensionBundleVersion="1.0.12" Version="7.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<ExtensionList>
|
||||
<Extension Id="com.openpype.PS.panel" Version="1.0.1" />
|
||||
</ExtensionList>
|
||||
|
|
|
|||
BIN
openpype/hosts/photoshop/api/extension/extension.zxp
Normal file
|
|
@ -32,17 +32,6 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#creator-button").bind("click", function() {
|
||||
RPC.call('Photoshop.creator_route').then(function (data) {
|
||||
}, function (error) {
|
||||
alert(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#loader-button").bind("click", function() {
|
||||
|
|
@ -75,17 +64,6 @@
|
|||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#subsetmanager-button").bind("click", function() {
|
||||
RPC.call('Photoshop.subsetmanager_route').then(function (data) {
|
||||
}, function (error) {
|
||||
alert(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
|
|
@ -109,11 +87,9 @@
|
|||
<script type="text/javascript" src="./client/client.js"></script>
|
||||
|
||||
<a href=# id=workfiles-button><button>Workfiles...</button></a>
|
||||
<a href=# id=creator-button><button>Create...</button></a>
|
||||
<a href=# id=loader-button><button>Load...</button></a>
|
||||
<a href=# id=publish-button><button>Publish...</button></a>
|
||||
<a href=# id=sceneinventory-button><button>Manage...</button></a>
|
||||
<a href=# id=subsetmanager-button><button>Subset Manager...</button></a>
|
||||
<a href=# id=experimental-button><button>Experimental Tools...</button></a>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -334,9 +334,6 @@ class PhotoshopRoute(WebSocketRoute):
|
|||
return await self.socket.call('photoshop.read')
|
||||
|
||||
# panel routes for tools
|
||||
async def creator_route(self):
|
||||
self._tool_route("creator")
|
||||
|
||||
async def workfiles_route(self):
|
||||
self._tool_route("workfiles")
|
||||
|
||||
|
|
@ -344,14 +341,11 @@ class PhotoshopRoute(WebSocketRoute):
|
|||
self._tool_route("loader")
|
||||
|
||||
async def publish_route(self):
|
||||
self._tool_route("publish")
|
||||
self._tool_route("publisher")
|
||||
|
||||
async def sceneinventory_route(self):
|
||||
self._tool_route("sceneinventory")
|
||||
|
||||
async def subsetmanager_route(self):
|
||||
self._tool_route("subsetmanager")
|
||||
|
||||
async def experimental_tools_route(self):
|
||||
self._tool_route("experimental_tools")
|
||||
|
||||
|
|
|
|||
|
|
@ -20,9 +20,11 @@ def safe_excepthook(*args):
|
|||
|
||||
|
||||
def main(*subprocess_args):
|
||||
from openpype.hosts.photoshop import api
|
||||
from openpype.hosts.photoshop.api import PhotoshopHost
|
||||
|
||||
host = PhotoshopHost()
|
||||
install_host(host)
|
||||
|
||||
install_host(api)
|
||||
sys.excepthook = safe_excepthook
|
||||
|
||||
# coloring in StdOutBroker
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import os
|
||||
from Qt import QtWidgets
|
||||
|
||||
import pyblish.api
|
||||
|
||||
from openpype.lib import register_event_callback, Logger
|
||||
|
|
@ -12,6 +11,14 @@ from openpype.pipeline import (
|
|||
deregister_creator_plugin_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
)
|
||||
|
||||
from openpype.host import (
|
||||
HostBase,
|
||||
IWorkfileHost,
|
||||
ILoadHost,
|
||||
IPublishHost
|
||||
)
|
||||
|
||||
from openpype.pipeline.load import any_outdated_containers
|
||||
from openpype.hosts.photoshop import PHOTOSHOP_HOST_DIR
|
||||
|
||||
|
|
@ -26,6 +33,140 @@ CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
|
|||
INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory")
|
||||
|
||||
|
||||
class PhotoshopHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
||||
name = "photoshop"
|
||||
|
||||
def install(self):
|
||||
"""Install Photoshop-specific functionality needed for integration.
|
||||
|
||||
This function is called automatically on calling
|
||||
`api.install(photoshop)`.
|
||||
"""
|
||||
log.info("Installing OpenPype Photoshop...")
|
||||
pyblish.api.register_host("photoshop")
|
||||
|
||||
pyblish.api.register_plugin_path(PUBLISH_PATH)
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
register_creator_plugin_path(CREATE_PATH)
|
||||
log.info(PUBLISH_PATH)
|
||||
|
||||
pyblish.api.register_callback(
|
||||
"instanceToggled", on_pyblish_instance_toggled
|
||||
)
|
||||
|
||||
register_event_callback("application.launched", on_application_launch)
|
||||
|
||||
def current_file(self):
|
||||
try:
|
||||
full_name = lib.stub().get_active_document_full_name()
|
||||
if full_name and full_name != "null":
|
||||
return os.path.normpath(full_name).replace("\\", "/")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
def work_root(self, session):
|
||||
return os.path.normpath(session["AVALON_WORKDIR"]).replace("\\", "/")
|
||||
|
||||
def open_workfile(self, filepath):
|
||||
lib.stub().open(filepath)
|
||||
|
||||
return True
|
||||
|
||||
def save_workfile(self, filepath=None):
|
||||
_, ext = os.path.splitext(filepath)
|
||||
lib.stub().saveAs(filepath, ext[1:], True)
|
||||
|
||||
def get_current_workfile(self):
|
||||
return self.current_file()
|
||||
|
||||
def workfile_has_unsaved_changes(self):
|
||||
if self.current_file():
|
||||
return not lib.stub().is_saved()
|
||||
|
||||
return False
|
||||
|
||||
def get_workfile_extensions(self):
|
||||
return [".psd", ".psb"]
|
||||
|
||||
def get_containers(self):
|
||||
return ls()
|
||||
|
||||
def get_context_data(self):
|
||||
"""Get stored values for context (validation enable/disable etc)"""
|
||||
meta = _get_stub().get_layers_metadata()
|
||||
for item in meta:
|
||||
if item.get("id") == "publish_context":
|
||||
item.pop("id")
|
||||
return item
|
||||
|
||||
return {}
|
||||
|
||||
def update_context_data(self, data, changes):
|
||||
"""Store value needed for context"""
|
||||
item = data
|
||||
item["id"] = "publish_context"
|
||||
_get_stub().imprint(item["id"], item)
|
||||
|
||||
def get_context_title(self):
|
||||
"""Returns title for Creator window"""
|
||||
|
||||
project_name = legacy_io.Session["AVALON_PROJECT"]
|
||||
asset_name = legacy_io.Session["AVALON_ASSET"]
|
||||
task_name = legacy_io.Session["AVALON_TASK"]
|
||||
return "{}/{}/{}".format(project_name, asset_name, task_name)
|
||||
|
||||
def list_instances(self):
|
||||
"""List all created instances to publish from current workfile.
|
||||
|
||||
Pulls from File > File Info
|
||||
|
||||
Returns:
|
||||
(list) of dictionaries matching instances format
|
||||
"""
|
||||
stub = _get_stub()
|
||||
|
||||
if not stub:
|
||||
return []
|
||||
|
||||
instances = []
|
||||
layers_meta = stub.get_layers_metadata()
|
||||
if layers_meta:
|
||||
for instance in layers_meta:
|
||||
if instance.get("id") == "pyblish.avalon.instance":
|
||||
instances.append(instance)
|
||||
|
||||
return instances
|
||||
|
||||
def remove_instance(self, instance):
|
||||
"""Remove instance from current workfile metadata.
|
||||
|
||||
Updates metadata of current file in File > File Info and removes
|
||||
icon highlight on group layer.
|
||||
|
||||
Args:
|
||||
instance (dict): instance representation from subsetmanager model
|
||||
"""
|
||||
stub = _get_stub()
|
||||
|
||||
if not stub:
|
||||
return
|
||||
|
||||
inst_id = instance.get("instance_id") or instance.get("uuid") # legacy
|
||||
if not inst_id:
|
||||
log.warning("No instance identifier for {}".format(instance))
|
||||
return
|
||||
|
||||
stub.remove_instance(inst_id)
|
||||
|
||||
if instance.get("members"):
|
||||
item = stub.get_layer(instance["members"][0])
|
||||
if item:
|
||||
stub.rename_layer(item.id,
|
||||
item.name.replace(stub.PUBLISH_ICON, ''))
|
||||
|
||||
|
||||
def check_inventory():
|
||||
if not any_outdated_containers():
|
||||
return
|
||||
|
|
@ -52,32 +193,6 @@ def on_pyblish_instance_toggled(instance, old_value, new_value):
|
|||
instance[0].Visible = new_value
|
||||
|
||||
|
||||
def install():
|
||||
"""Install Photoshop-specific functionality of avalon-core.
|
||||
|
||||
This function is called automatically on calling `api.install(photoshop)`.
|
||||
"""
|
||||
log.info("Installing OpenPype Photoshop...")
|
||||
pyblish.api.register_host("photoshop")
|
||||
|
||||
pyblish.api.register_plugin_path(PUBLISH_PATH)
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
register_creator_plugin_path(CREATE_PATH)
|
||||
log.info(PUBLISH_PATH)
|
||||
|
||||
pyblish.api.register_callback(
|
||||
"instanceToggled", on_pyblish_instance_toggled
|
||||
)
|
||||
|
||||
register_event_callback("application.launched", on_application_launch)
|
||||
|
||||
|
||||
def uninstall():
|
||||
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
|
||||
deregister_loader_plugin_path(LOAD_PATH)
|
||||
deregister_creator_plugin_path(CREATE_PATH)
|
||||
|
||||
|
||||
def ls():
|
||||
"""Yields containers from active Photoshop document
|
||||
|
||||
|
|
@ -117,61 +232,6 @@ def ls():
|
|||
yield data
|
||||
|
||||
|
||||
def list_instances():
|
||||
"""List all created instances to publish from current workfile.
|
||||
|
||||
Pulls from File > File Info
|
||||
|
||||
For SubsetManager
|
||||
|
||||
Returns:
|
||||
(list) of dictionaries matching instances format
|
||||
"""
|
||||
stub = _get_stub()
|
||||
|
||||
if not stub:
|
||||
return []
|
||||
|
||||
instances = []
|
||||
layers_meta = stub.get_layers_metadata()
|
||||
if layers_meta:
|
||||
for instance in layers_meta:
|
||||
if instance.get("id") == "pyblish.avalon.instance":
|
||||
instances.append(instance)
|
||||
|
||||
return instances
|
||||
|
||||
|
||||
def remove_instance(instance):
|
||||
"""Remove instance from current workfile metadata.
|
||||
|
||||
Updates metadata of current file in File > File Info and removes
|
||||
icon highlight on group layer.
|
||||
|
||||
For SubsetManager
|
||||
|
||||
Args:
|
||||
instance (dict): instance representation from subsetmanager model
|
||||
"""
|
||||
stub = _get_stub()
|
||||
|
||||
if not stub:
|
||||
return
|
||||
|
||||
inst_id = instance.get("instance_id") or instance.get("uuid") # legacy
|
||||
if not inst_id:
|
||||
log.warning("No instance identifier for {}".format(instance))
|
||||
return
|
||||
|
||||
stub.remove_instance(inst_id)
|
||||
|
||||
if instance.get("members"):
|
||||
item = stub.get_layer(instance["members"][0])
|
||||
if item:
|
||||
stub.rename_layer(item.id,
|
||||
item.name.replace(stub.PUBLISH_ICON, ''))
|
||||
|
||||
|
||||
def _get_stub():
|
||||
"""Handle pulling stub from PS to run operations on host
|
||||
|
||||
|
|
@ -226,28 +286,17 @@ def containerise(
|
|||
return layer
|
||||
|
||||
|
||||
def get_context_data():
|
||||
"""Get stored values for context (validation enable/disable etc)"""
|
||||
meta = _get_stub().get_layers_metadata()
|
||||
for item in meta:
|
||||
if item.get("id") == "publish_context":
|
||||
item.pop("id")
|
||||
return item
|
||||
def cache_and_get_instances(creator):
|
||||
"""Cache instances in shared data.
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
def update_context_data(data, changes):
|
||||
"""Store value needed for context"""
|
||||
item = data
|
||||
item["id"] = "publish_context"
|
||||
_get_stub().imprint(item["id"], item)
|
||||
|
||||
|
||||
def get_context_title():
|
||||
"""Returns title for Creator window"""
|
||||
|
||||
project_name = legacy_io.Session["AVALON_PROJECT"]
|
||||
asset_name = legacy_io.Session["AVALON_ASSET"]
|
||||
task_name = legacy_io.Session["AVALON_TASK"]
|
||||
return "{}/{}/{}".format(project_name, asset_name, task_name)
|
||||
Storing all instances as a list as legacy instances might be still present.
|
||||
Args:
|
||||
creator (Creator): Plugin which would like to get instances from host.
|
||||
Returns:
|
||||
List[]: list of all instances stored in metadata
|
||||
"""
|
||||
shared_key = "openpype.photoshop.instances"
|
||||
if shared_key not in creator.collection_shared_data:
|
||||
creator.collection_shared_data[shared_key] = \
|
||||
creator.host.list_instances()
|
||||
return creator.collection_shared_data[shared_key]
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
"""Host API required Work Files tool"""
|
||||
import os
|
||||
|
||||
from . import lib
|
||||
|
||||
|
||||
def _active_document():
|
||||
document_name = lib.stub().get_active_document_name()
|
||||
if not document_name:
|
||||
return None
|
||||
|
||||
return document_name
|
||||
|
||||
|
||||
def file_extensions():
|
||||
return [".psd", ".psb"]
|
||||
|
||||
|
||||
def has_unsaved_changes():
|
||||
if _active_document():
|
||||
return not lib.stub().is_saved()
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def save_file(filepath):
|
||||
_, ext = os.path.splitext(filepath)
|
||||
lib.stub().saveAs(filepath, ext[1:], True)
|
||||
|
||||
|
||||
def open_file(filepath):
|
||||
lib.stub().open(filepath)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def current_file():
|
||||
try:
|
||||
full_name = lib.stub().get_active_document_full_name()
|
||||
if full_name and full_name != "null":
|
||||
return os.path.normpath(full_name).replace("\\", "/")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def work_root(session):
|
||||
return os.path.normpath(session["AVALON_WORKDIR"]).replace("\\", "/")
|
||||
|
|
@ -9,6 +9,7 @@ from openpype.pipeline import (
|
|||
)
|
||||
from openpype.lib import prepare_template_data
|
||||
from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS
|
||||
from openpype.hosts.photoshop.api.pipeline import cache_and_get_instances
|
||||
|
||||
|
||||
class ImageCreator(Creator):
|
||||
|
|
@ -19,7 +20,7 @@ class ImageCreator(Creator):
|
|||
description = "Image creator"
|
||||
|
||||
def collect_instances(self):
|
||||
for instance_data in api.list_instances():
|
||||
for instance_data in cache_and_get_instances(self):
|
||||
# legacy instances have family=='image'
|
||||
creator_id = (instance_data.get("creator_identifier") or
|
||||
instance_data.get("family"))
|
||||
|
|
@ -97,6 +98,7 @@ class ImageCreator(Creator):
|
|||
|
||||
data.update({"subset": subset_name})
|
||||
data.update({"members": [str(group.id)]})
|
||||
data.update({"layer_name": layer_name})
|
||||
data.update({"long_name": "_".join(layer_names_in_hierarchy)})
|
||||
|
||||
new_instance = CreatedInstance(self.family, subset_name, data,
|
||||
|
|
@ -121,7 +123,7 @@ class ImageCreator(Creator):
|
|||
|
||||
def remove_instances(self, instances):
|
||||
for instance in instances:
|
||||
api.remove_instance(instance)
|
||||
self.host.remove_instance(instance)
|
||||
self._remove_instance_from_context(instance)
|
||||
|
||||
def get_default_variants(self):
|
||||
|
|
@ -163,6 +165,11 @@ class ImageCreator(Creator):
|
|||
def _clean_highlights(self, stub, item):
|
||||
return item.replace(stub.PUBLISH_ICON, '').replace(stub.LOADED_ICON,
|
||||
'')
|
||||
@classmethod
|
||||
def get_dynamic_data(cls, *args, **kwargs):
|
||||
|
||||
def get_dynamic_data(self, variant, task_name, asset_doc,
|
||||
project_name, host_name, instance):
|
||||
if instance is not None:
|
||||
layer_name = instance.get("layer_name")
|
||||
if layer_name:
|
||||
return {"layer": layer_name}
|
||||
return {"layer": "{layer}"}
|
||||
|
|
|
|||
|
|
@ -1,119 +0,0 @@
|
|||
import re
|
||||
|
||||
from Qt import QtWidgets
|
||||
from openpype.pipeline import create
|
||||
from openpype.hosts.photoshop import api as photoshop
|
||||
|
||||
from openpype.lib import prepare_template_data
|
||||
from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS
|
||||
|
||||
|
||||
class CreateImage(create.LegacyCreator):
|
||||
"""Image folder for publish."""
|
||||
|
||||
name = "imageDefault"
|
||||
label = "Image"
|
||||
family = "image"
|
||||
defaults = ["Main"]
|
||||
|
||||
def process(self):
|
||||
groups = []
|
||||
layers = []
|
||||
create_group = False
|
||||
|
||||
stub = photoshop.stub()
|
||||
if (self.options or {}).get("useSelection"):
|
||||
multiple_instances = False
|
||||
selection = stub.get_selected_layers()
|
||||
self.log.info("selection {}".format(selection))
|
||||
if len(selection) > 1:
|
||||
# Ask user whether to create one image or image per selected
|
||||
# item.
|
||||
msg_box = QtWidgets.QMessageBox()
|
||||
msg_box.setIcon(QtWidgets.QMessageBox.Warning)
|
||||
msg_box.setText(
|
||||
"Multiple layers selected."
|
||||
"\nDo you want to make one image per layer?"
|
||||
)
|
||||
msg_box.setStandardButtons(
|
||||
QtWidgets.QMessageBox.Yes |
|
||||
QtWidgets.QMessageBox.No |
|
||||
QtWidgets.QMessageBox.Cancel
|
||||
)
|
||||
ret = msg_box.exec_()
|
||||
if ret == QtWidgets.QMessageBox.Yes:
|
||||
multiple_instances = True
|
||||
elif ret == QtWidgets.QMessageBox.Cancel:
|
||||
return
|
||||
|
||||
if multiple_instances:
|
||||
for item in selection:
|
||||
if item.group:
|
||||
groups.append(item)
|
||||
else:
|
||||
layers.append(item)
|
||||
else:
|
||||
group = stub.group_selected_layers(self.name)
|
||||
groups.append(group)
|
||||
|
||||
elif len(selection) == 1:
|
||||
# One selected item. Use group if its a LayerSet (group), else
|
||||
# create a new group.
|
||||
if selection[0].group:
|
||||
groups.append(selection[0])
|
||||
else:
|
||||
layers.append(selection[0])
|
||||
elif len(selection) == 0:
|
||||
# No selection creates an empty group.
|
||||
create_group = True
|
||||
else:
|
||||
group = stub.create_group(self.name)
|
||||
groups.append(group)
|
||||
|
||||
if create_group:
|
||||
group = stub.create_group(self.name)
|
||||
groups.append(group)
|
||||
|
||||
for layer in layers:
|
||||
stub.select_layers([layer])
|
||||
group = stub.group_selected_layers(layer.name)
|
||||
groups.append(group)
|
||||
|
||||
creator_subset_name = self.data["subset"]
|
||||
layer_name = ''
|
||||
for group in groups:
|
||||
long_names = []
|
||||
group.name = group.name.replace(stub.PUBLISH_ICON, ''). \
|
||||
replace(stub.LOADED_ICON, '')
|
||||
|
||||
subset_name = creator_subset_name
|
||||
if len(groups) > 1:
|
||||
layer_name = re.sub(
|
||||
"[^{}]+".format(SUBSET_NAME_ALLOWED_SYMBOLS),
|
||||
"",
|
||||
group.name
|
||||
)
|
||||
if "{layer}" not in subset_name.lower():
|
||||
subset_name += "{Layer}"
|
||||
|
||||
layer_fill = prepare_template_data({"layer": layer_name})
|
||||
subset_name = subset_name.format(**layer_fill)
|
||||
|
||||
if group.long_name:
|
||||
for directory in group.long_name[::-1]:
|
||||
name = directory.replace(stub.PUBLISH_ICON, '').\
|
||||
replace(stub.LOADED_ICON, '')
|
||||
long_names.append(name)
|
||||
|
||||
self.data.update({"subset": subset_name})
|
||||
self.data.update({"uuid": str(group.id)})
|
||||
self.data.update({"members": [str(group.id)]})
|
||||
self.data.update({"long_name": "_".join(long_names)})
|
||||
stub.imprint(group, self.data)
|
||||
# reusing existing group, need to rename afterwards
|
||||
if not create_group:
|
||||
stub.rename_layer(group.id, stub.PUBLISH_ICON + group.name)
|
||||
|
||||
@classmethod
|
||||
def get_dynamic_data(cls, *args, **kwargs):
|
||||
return {"layer": "{layer}"}
|
||||
|
|
@ -5,6 +5,7 @@ from openpype.pipeline import (
|
|||
CreatedInstance,
|
||||
legacy_io
|
||||
)
|
||||
from openpype.hosts.photoshop.api.pipeline import cache_and_get_instances
|
||||
|
||||
|
||||
class PSWorkfileCreator(AutoCreator):
|
||||
|
|
@ -17,7 +18,7 @@ class PSWorkfileCreator(AutoCreator):
|
|||
return []
|
||||
|
||||
def collect_instances(self):
|
||||
for instance_data in api.list_instances():
|
||||
for instance_data in cache_and_get_instances(self):
|
||||
creator_id = instance_data.get("creator_identifier")
|
||||
if creator_id == self.identifier:
|
||||
subset_name = instance_data["subset"]
|
||||
|
|
@ -54,7 +55,7 @@ class PSWorkfileCreator(AutoCreator):
|
|||
}
|
||||
data.update(self.get_dynamic_data(
|
||||
self.default_variant, task_name, asset_doc,
|
||||
project_name, host_name
|
||||
project_name, host_name, None
|
||||
))
|
||||
|
||||
new_instance = CreatedInstance(
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class CollectExtensionVersion(pyblish.api.ContextPlugin):
|
|||
with open(manifest_url) as fp:
|
||||
content = fp.read()
|
||||
|
||||
found = re.findall(r'(ExtensionBundleVersion=")([0-10\.]+)(")',
|
||||
found = re.findall(r'(ExtensionBundleVersion=")([0-9\.]+)(")',
|
||||
content)
|
||||
if found:
|
||||
expected_version = found[0][1]
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ class CollectInstances(pyblish.api.ContextPlugin):
|
|||
|
||||
if len(instance_names) != len(set(instance_names)):
|
||||
self.log.warning("Duplicate instances found. " +
|
||||
"Remove unwanted via SubsetManager")
|
||||
"Remove unwanted via Publisher")
|
||||
|
||||
if len(instance_names) == 0 and self.flatten_subset_template:
|
||||
project_name = context.data["projectEntity"]["name"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<error id="main">
|
||||
<title>Asset does not match</title>
|
||||
<description>
|
||||
## Collected asset name is not same as in context
|
||||
|
||||
{msg}
|
||||
### How to repair?
|
||||
{repair_msg}
|
||||
Refresh Publish afterwards (circle arrow at the bottom right).
|
||||
|
||||
If that's not correct value, close workfile and reopen via Workfiles to get
|
||||
proper context asset name OR disable this validator and publish again
|
||||
if you are publishing to different context deliberately.
|
||||
|
||||
(Context means combination of project, asset name and task name.)
|
||||
</description>
|
||||
</error>
|
||||
</root>
|
||||
|
|
@ -10,7 +10,7 @@ Subset or layer name cannot contain specific characters (spaces etc) which could
|
|||
|
||||
### How to repair?
|
||||
|
||||
You can fix this with "repair" button on the right.
|
||||
You can fix this with "repair" button on the right and press Refresh publishing button at the bottom right.
|
||||
</description>
|
||||
<detail>
|
||||
### __Detailed Info__ (optional)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import pyblish.api
|
||||
|
||||
from openpype.pipeline import legacy_io
|
||||
from openpype.pipeline.publish import ValidateContentsOrder
|
||||
from openpype.pipeline.publish import (
|
||||
ValidateContentsOrder,
|
||||
PublishXmlValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
from openpype.hosts.photoshop import api as photoshop
|
||||
|
||||
|
||||
|
|
@ -31,30 +35,38 @@ class ValidateInstanceAssetRepair(pyblish.api.Action):
|
|||
stub.imprint(instance[0], data)
|
||||
|
||||
|
||||
class ValidateInstanceAsset(pyblish.api.InstancePlugin):
|
||||
class ValidateInstanceAsset(OptionalPyblishPluginMixin,
|
||||
pyblish.api.InstancePlugin):
|
||||
"""Validate the instance asset is the current selected context asset.
|
||||
|
||||
As it might happen that multiple worfiles are opened, switching
|
||||
between them would mess with selected context.
|
||||
In that case outputs might be output under wrong asset!
|
||||
As it might happen that multiple worfiles are opened, switching
|
||||
between them would mess with selected context.
|
||||
In that case outputs might be output under wrong asset!
|
||||
|
||||
Repair action will use Context asset value (from Workfiles or Launcher)
|
||||
Closing and reopening with Workfiles will refresh Context value.
|
||||
Repair action will use Context asset value (from Workfiles or Launcher)
|
||||
Closing and reopening with Workfiles will refresh Context value.
|
||||
"""
|
||||
|
||||
label = "Validate Instance Asset"
|
||||
hosts = ["photoshop"]
|
||||
optional = True
|
||||
actions = [ValidateInstanceAssetRepair]
|
||||
order = ValidateContentsOrder
|
||||
|
||||
def process(self, instance):
|
||||
instance_asset = instance.data["asset"]
|
||||
current_asset = legacy_io.Session["AVALON_ASSET"]
|
||||
msg = (
|
||||
f"Instance asset {instance_asset} is not the same "
|
||||
f"as current context {current_asset}. PLEASE DO:\n"
|
||||
f"Repair with 'A' action to use '{current_asset}'.\n"
|
||||
f"If that's not correct value, close workfile and "
|
||||
f"reopen via Workfiles!"
|
||||
)
|
||||
assert instance_asset == current_asset, msg
|
||||
|
||||
if instance_asset != current_asset:
|
||||
msg = (
|
||||
f"Instance asset {instance_asset} is not the same "
|
||||
f"as current context {current_asset}."
|
||||
|
||||
)
|
||||
repair_msg = (
|
||||
f"Repair with 'Repair' button to use '{current_asset}'.\n"
|
||||
)
|
||||
formatting_data = {"msg": msg,
|
||||
"repair_msg": repair_msg}
|
||||
raise PublishXmlValidationError(self, msg,
|
||||
formatting_data=formatting_data)
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ class ValidateNaming(pyblish.api.InstancePlugin):
|
|||
replace_char = ''
|
||||
|
||||
def process(self, instance):
|
||||
help_msg = ' Use Repair action (A) in Pyblish to fix it.'
|
||||
help_msg = ' Use Repair button to fix it and then refresh publish.'
|
||||
|
||||
layer = instance.data.get("layer")
|
||||
if layer:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class ValidateSubsetUniqueness(pyblish.api.ContextPlugin):
|
|||
for item, count in collections.Counter(subset_names).items()
|
||||
if count > 1]
|
||||
msg = ("Instance subset names {} are not unique. ".format(non_unique) +
|
||||
"Remove duplicates via SubsetManager.")
|
||||
"Remove duplicates via Publisher.")
|
||||
formatting_data = {
|
||||
"non_unique": ",".join(non_unique)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ class FtrackTrayWrapper:
|
|||
failed_count = 0
|
||||
|
||||
# If thread failed test Ftrack and Mongo connection
|
||||
elif not self.thread_socket_server.isAlive():
|
||||
elif not self.thread_socket_server.is_alive():
|
||||
self.thread_socket_server.join()
|
||||
self.thread_socket_server = None
|
||||
ftrack_accessible = False
|
||||
|
|
|
|||
|
|
@ -88,7 +88,10 @@ class ExperimentalTools:
|
|||
"publisher",
|
||||
"New publisher",
|
||||
"Combined creation and publishing into one tool.",
|
||||
self._show_publisher
|
||||
self._show_publisher,
|
||||
hosts_filter=["blender", "maya", "nuke", "celaction", "flame",
|
||||
"fusion", "harmony", "hiero", "resolve",
|
||||
"tvpaint", "unreal"]
|
||||
)
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ class PluginLoadReportModel(QtGui.QStandardItemModel):
|
|||
parent = self.invisibleRootItem()
|
||||
parent.removeRows(0, parent.rowCount())
|
||||
|
||||
if report is None:
|
||||
return
|
||||
|
||||
new_items = []
|
||||
new_items_by_filepath = {}
|
||||
for filepath in report.crashed_plugin_paths.keys():
|
||||
|
|
|
|||
|
|
@ -367,6 +367,7 @@ class LoadedFilesView(QtWidgets.QTreeView):
|
|||
def _on_rows_inserted(self):
|
||||
header = self.header()
|
||||
header.resizeSections(header.ResizeToContents)
|
||||
self._update_remove_btn()
|
||||
|
||||
def resizeEvent(self, event):
|
||||
super(LoadedFilesView, self).resizeEvent(event)
|
||||
|
|
|
|||
|
|
@ -361,6 +361,13 @@ class PublisherWindow(QtWidgets.QDialog):
|
|||
super(PublisherWindow, self).resizeEvent(event)
|
||||
self._update_publish_frame_rect()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
# Ignore escape button to close window
|
||||
if event.key() == QtCore.Qt.Key_Escape:
|
||||
event.accept()
|
||||
return
|
||||
super(PublisherWindow, self).keyPressEvent(event)
|
||||
|
||||
def _on_overlay_message(self, event):
|
||||
self._overlay_object.add_message(
|
||||
event["message"],
|
||||
|
|
|
|||
1049
poetry.lock
generated
|
|
@ -28,11 +28,11 @@ packages = [
|
|||
openpype = 'start:boot'
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "3.7.*"
|
||||
python = ">=3.9.1,<3.10"
|
||||
aiohttp = "^3.7"
|
||||
aiohttp_json_rpc = "*" # TVPaint server
|
||||
acre = { git = "https://github.com/pypeclub/acre.git" }
|
||||
opentimelineio = { version = "0.14.0.dev1", source = "openpype" }
|
||||
opentimelineio = "^0.14"
|
||||
appdirs = { git = "https://github.com/ActiveState/appdirs.git", branch = "master" }
|
||||
blessed = "^1.17" # openpype terminal formatting
|
||||
coolname = "*"
|
||||
|
|
@ -75,7 +75,7 @@ aiohttp-middlewares = "^2.0.0"
|
|||
flake8 = "^3.7"
|
||||
autopep8 = "^1.4"
|
||||
coverage = "*"
|
||||
cx_freeze = "~6.9"
|
||||
cx_freeze = "6.12.0"
|
||||
GitPython = "^3.1.17"
|
||||
jedi = "^0.13"
|
||||
Jinja2 = "^2.11"
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ done
|
|||
###############################################################################
|
||||
detect_python () {
|
||||
echo -e "${BIGreen}>>>${RST} Using python \c"
|
||||
command -v python >/dev/null 2>&1 || { echo -e "${BIRed}- NOT FOUND${RST} ${BIYellow}You need Python 3.7 installed to continue.${RST}"; return 1; }
|
||||
command -v python >/dev/null 2>&1 || { echo -e "${BIRed}- NOT FOUND${RST} ${BIYellow}You need Python 3.9 installed to continue.${RST}"; return 1; }
|
||||
local version_command
|
||||
version_command="import sys;print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))"
|
||||
local python_version
|
||||
|
|
@ -99,9 +99,9 @@ detect_python () {
|
|||
IFS=.
|
||||
set -- $python_version
|
||||
IFS="$oIFS"
|
||||
if [ "$1" -ge "3" ] && [ "$2" -ge "6" ] ; then
|
||||
if [ "$2" -gt "7" ] ; then
|
||||
echo -e "${BIWhite}[${RST} ${BIRed}$1.$2 ${BIWhite}]${RST} - ${BIRed}FAILED${RST} ${BIYellow}Version is new and unsupported, use${RST} ${BIPurple}3.7.x${RST}"; return 1;
|
||||
if [ "$1" -ge "3" ] && [ "$2" -ge "9" ] ; then
|
||||
if [ "$2" -gt "9" ] ; then
|
||||
echo -e "${BIWhite}[${RST} ${BIRed}$1.$2 ${BIWhite}]${RST} - ${BIRed}FAILED${RST} ${BIYellow}Version is new and unsupported, use${RST} ${BIPurple}3.9.x${RST}"; return 1;
|
||||
else
|
||||
echo -e "${BIWhite}[${RST} ${BIGreen}$1.$2${RST} ${BIWhite}]${RST}"
|
||||
fi
|
||||
|
|
@ -152,7 +152,7 @@ main () {
|
|||
openpype_root=$(dirname $(dirname "$(realpath ${BASH_SOURCE[0]})"))
|
||||
pushd "$openpype_root" > /dev/null || return > /dev/null
|
||||
|
||||
version_command="import os;exec(open(os.path.join('$openpype_root', 'openpype', 'version.py')).read());print(__version__);"
|
||||
version_command="import os;import re;version={};exec(open(os.path.join('$openpype_root', 'openpype', 'version.py')).read(), version);print(re.search(r'(\d+\.\d+.\d+).*', version['__version__'])[1]);"
|
||||
openpype_version="$(python <<< ${version_command})"
|
||||
|
||||
_inside_openpype_tool="1"
|
||||
|
|
@ -181,7 +181,7 @@ if [ "$disable_submodule_update" == 1 ]; then
|
|||
echo -e "${BIYellow}***${RST} Not updating submodules ..."
|
||||
else
|
||||
echo -e "${BIGreen}>>>${RST} Making sure submodules are up-to-date ..."
|
||||
git submodule update --init --recursive
|
||||
git submodule update --init --recursive || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return 1; }
|
||||
fi
|
||||
echo -e "${BIGreen}>>>${RST} Building ..."
|
||||
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||
|
|
@ -189,12 +189,21 @@ if [ "$disable_submodule_update" == 1 ]; then
|
|||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
"$POETRY_HOME/bin/poetry" run python "$openpype_root/setup.py" bdist_mac &> "$openpype_root/build/build.log" || { echo -e "${BIRed}------------------------------------------${RST}"; cat "$openpype_root/build/build.log"; echo -e "${BIRed}------------------------------------------${RST}"; echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return 1; }
|
||||
fi
|
||||
"$POETRY_HOME/bin/poetry" run python "$openpype_root/tools/build_dependencies.py"
|
||||
"$POETRY_HOME/bin/poetry" run python "$openpype_root/tools/build_dependencies.py" || { echo -e "${BIRed}!!!>${RST} ${BIYellow}Failed to process dependencies${RST}"; return 1; }
|
||||
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# fix cx_Freeze libs issue
|
||||
echo -e "${BIGreen}>>>${RST} Fixing libs ..."
|
||||
mv "$openpype_root/build/OpenPype $openpype_version.app/Contents/MacOS/dependencies/cx_Freeze" "$openpype_root/build/OpenPype $openpype_version.app/Contents/MacOS/lib/" || { echo -e "${BIRed}!!!>${RST} ${BIYellow}Can't move cx_Freeze libs${RST}"; return 1; }
|
||||
|
||||
|
||||
# fix code signing issue
|
||||
codesign --remove-signature "$openpype_root/build/OpenPype $openpype_version.app/Contents/MacOS/lib/Python"
|
||||
echo -e "${BIGreen}>>>${RST} Fixing code signatures ...\c"
|
||||
codesign --remove-signature "$openpype_root/build/OpenPype $openpype_version.app/Contents/MacOS/openpype_console" || { echo -e "${BIRed}FAILED${RST}"; return 1; }
|
||||
codesign --remove-signature "$openpype_root/build/OpenPype $openpype_version.app/Contents/MacOS/openpype_gui" || { echo -e "${BIRed}FAILED${RST}"; return 1; }
|
||||
echo -e "${BIGreen}DONE${RST}"
|
||||
if command -v create-dmg > /dev/null 2>&1; then
|
||||
echo -e "${BIGreen}>>>${RST} Creating dmg image ...\c"
|
||||
create-dmg \
|
||||
--volname "OpenPype $openpype_version Installer" \
|
||||
--window-pos 200 120 \
|
||||
|
|
@ -202,6 +211,9 @@ if [ "$disable_submodule_update" == 1 ]; then
|
|||
--app-drop-link 100 50 \
|
||||
"$openpype_root/build/OpenPype-Installer-$openpype_version.dmg" \
|
||||
"$openpype_root/build/OpenPype $openpype_version.app"
|
||||
|
||||
test $? -eq 0 || { echo -e "${BIRed}FAILED${RST}"; return 1; }
|
||||
echo -e "${BIGreen}DONE${RST}"
|
||||
else
|
||||
echo -e "${BIYellow}!!!${RST} ${BIWhite}create-dmg${RST} command is not available."
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -131,15 +131,15 @@ if(-not $m) {
|
|||
Set-Location -Path $current_dir
|
||||
Exit-WithCode 1
|
||||
}
|
||||
# We are supporting python 3.7 only
|
||||
if (($matches[1] -lt 3) -or ($matches[2] -lt 7)) {
|
||||
# We are supporting python 3.9
|
||||
if (($matches[1] -lt 3) -or ($matches[2] -lt 9)) {
|
||||
Write-Host "FAILED Version [ $p ] is old and unsupported" -ForegroundColor red
|
||||
Set-Location -Path $current_dir
|
||||
Exit-WithCode 1
|
||||
} elseif (($matches[1] -eq 3) -and ($matches[2] -gt 7)) {
|
||||
} elseif (($matches[1] -eq 3) -and ($matches[2] -gt 9)) {
|
||||
Write-Host "WARNING Version [ $p ] is unsupported, use at your own risk." -ForegroundColor yellow
|
||||
Write-Host "*** " -NoNewline -ForegroundColor yellow
|
||||
Write-Host "OpenPype supports only Python 3.7" -ForegroundColor white
|
||||
Write-Host "OpenPype supports only Python 3.9" -ForegroundColor white
|
||||
} else {
|
||||
Write-Host "OK [ $p ]" -ForegroundColor green
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,14 +100,14 @@ print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))
|
|||
Set-Location -Path $current_dir
|
||||
Exit-WithCode 1
|
||||
}
|
||||
# We are supporting python 3.7 only
|
||||
if (($matches[1] -lt 3) -or ($matches[2] -lt 7)) {
|
||||
# We are supporting python 3.9 only
|
||||
if (($matches[1] -lt 3) -or ($matches[2] -lt 9)) {
|
||||
Write-Color -Text "FAILED ", "Version ", "[", $p ,"]", "is old and unsupported" -Color Red, Yellow, Cyan, White, Cyan, Yellow
|
||||
Set-Location -Path $current_dir
|
||||
Exit-WithCode 1
|
||||
} elseif (($matches[1] -eq 3) -and ($matches[2] -gt 7)) {
|
||||
} elseif (($matches[1] -eq 3) -and ($matches[2] -gt 9)) {
|
||||
Write-Color -Text "WARNING Version ", "[", $p, "]", " is unsupported, use at your own risk." -Color Yellow, Cyan, White, Cyan, Yellow
|
||||
Write-Color -Text "*** ", "OpenPype supports only Python 3.7" -Color Yellow, White
|
||||
Write-Color -Text "*** ", "OpenPype supports only Python 3.9" -Color Yellow, White
|
||||
} else {
|
||||
Write-Color "OK ", "[", $p, "]" -Color Green, Cyan, White, Cyan
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,16 +88,16 @@ done
|
|||
###############################################################################
|
||||
detect_python () {
|
||||
echo -e "${BIGreen}>>>${RST} Using python \c"
|
||||
command -v python >/dev/null 2>&1 || { echo -e "${BIRed}- NOT FOUND${RST} ${BIYellow}You need Python 3.7 installed to continue.${RST}"; return 1; }
|
||||
command -v python >/dev/null 2>&1 || { echo -e "${BIRed}- NOT FOUND${RST} ${BIYellow}You need Python 3.9 installed to continue.${RST}"; return 1; }
|
||||
local version_command="import sys;print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))"
|
||||
local python_version="$(python <<< ${version_command})"
|
||||
oIFS="$IFS"
|
||||
IFS=.
|
||||
set -- $python_version
|
||||
IFS="$oIFS"
|
||||
if [ "$1" -ge "3" ] && [ "$2" -ge "6" ] ; then
|
||||
if [ "$2" -gt "7" ] ; then
|
||||
echo -e "${BIWhite}[${RST} ${BIRed}$1.$2 ${BIWhite}]${RST} - ${BIRed}FAILED${RST} ${BIYellow}Version is new and unsupported, use${RST} ${BIPurple}3.7.x${RST}"; return 1;
|
||||
if [ "$1" -ge "3" ] && [ "$2" -ge "9" ] ; then
|
||||
if [ "$2" -gt "9" ] ; then
|
||||
echo -e "${BIWhite}[${RST} ${BIRed}$1.$2 ${BIWhite}]${RST} - ${BIRed}FAILED${RST} ${BIYellow}Version is new and unsupported, use${RST} ${BIPurple}3.9.x${RST}"; return 1;
|
||||
else
|
||||
echo -e "${BIWhite}[${RST} ${BIGreen}$1.$2${RST} ${BIWhite}]${RST}"
|
||||
fi
|
||||
|
|
@ -192,8 +192,6 @@ main () {
|
|||
echo -e "${BIGreen}>>>${RST} Post-venv creation fixes ..."
|
||||
local openpype_index=$("$POETRY_HOME/bin/poetry" run python "$openpype_root/tools/parse_pyproject.py" tool.poetry.source.0.url)
|
||||
echo -e "${BIGreen}- ${RST} Using index: ${BIWhite}$openpype_index${RST}"
|
||||
"$POETRY_HOME/bin/poetry" run pip install setuptools==49.6.0
|
||||
"$POETRY_HOME/bin/poetry" run pip install --disable-pip-version-check --force-reinstall wheel
|
||||
"$POETRY_HOME/bin/poetry" run python -m pip install --disable-pip-version-check --force-reinstall pip
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,9 +78,9 @@ detect_python () {
|
|||
IFS=.
|
||||
set -- $python_version
|
||||
IFS="$oIFS"
|
||||
if [ "$1" -ge "3" ] && [ "$2" -ge "6" ] ; then
|
||||
if [ "$2" -gt "7" ] ; then
|
||||
echo -e "${BIWhite}[${RST} ${BIRed}$1.$2 ${BIWhite}]${RST} - ${BIRed}FAILED${RST} ${BIYellow}Version is new and unsupported, use${RST} ${BIPurple}3.7.x${RST}"; return 1;
|
||||
if [ "$1" -ge "3" ] && [ "$2" -ge "9" ] ; then
|
||||
if [ "$2" -gt "9" ] ; then
|
||||
echo -e "${BIWhite}[${RST} ${BIRed}$1.$2 ${BIWhite}]${RST} - ${BIRed}FAILED${RST} ${BIYellow}Version is new and unsupported, use${RST} ${BIPurple}3.9.x${RST}"; return 1;
|
||||
else
|
||||
echo -e "${BIWhite}[${RST} ${BIGreen}$1.$2${RST} ${BIWhite}]${RST}"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@ Distribution consists of two parts
|
|||
It is self contained (frozen) software that also includes all of the OpenPype codebase with the version
|
||||
from the time of the build.
|
||||
|
||||
Igniter package is around 500MB and preparing an updated version requires you to re-build pype. That would be
|
||||
Igniter package is around 1Gb and preparing an updated version requires you to re-build pype. That would be
|
||||
inconvenient for regular and quick distribution of production updates and fixes. So you can distribute those
|
||||
independently, without requiring you artists to re-install every time.
|
||||
|
||||
You can have multiple versions installed at the same time.
|
||||
|
||||
### 2. OpenPype Codebase
|
||||
|
||||
When you upgrade your studio pype deployment to a new version or make any local code changes, you can distribute
|
||||
|
|
@ -77,4 +79,6 @@ For example OpenPype will consider the versions in this order: `3.8.0-nightly` <
|
|||
|
||||
See https://semver.org/ for more details.
|
||||
|
||||
For studios customizing the source code of OpenPype, a practical approach could be to build by adding a name and a number after the PATCH and not to deploy 3.8.0 from original OpenPype repository. For example, your builds will be: `3.8.0-yourstudio.1` < `3.8.0-yourstudio.2` < `3.8.1-yourstudio.1`.
|
||||
For studios customizing the source code of OpenPype, a practical approach could be to build by adding a name and a number after the PATCH and not to deploy 3.8.0 from original OpenPype repository. For example, your builds will be: `3.8.0-yourstudio.1` < `3.8.0-yourstudio.2` < `3.8.1-yourstudio.1`.
|
||||
|
||||
Versions of Igniter and those coming in zips are compatible if they match major and minor version - `3.13.4` is compatible with `3.13.1` but not with `3.12.2` or `3.14.0`.
|
||||
|
|
|
|||
|
|
@ -38,34 +38,67 @@ In AfterEffects you'll find the tools in the `OpenPype` extension:
|
|||
|
||||
You can show the extension panel by going to `Window` > `Extensions` > `OpenPype`.
|
||||
|
||||
### Create
|
||||
|
||||
When you have created an composition you want to publish, you will need to tag existing composition. To do this open the `Creator` through the extensions `Create` button.
|
||||
|
||||

|
||||
|
||||
Because of current rendering limitations, it is expected that only single composition will be marked for publishing!
|
||||
|
||||
After Creator is successfully triggered on selected composition, it will be marked with an icon and its color
|
||||
will be changed.
|
||||
|
||||

|
||||
|
||||
### Publish
|
||||
|
||||
When you are ready to share some work, you will need to publish it. This is done by opening the `Publisher` through the `Publish...` button.
|
||||
|
||||
There is always instance for workfile created automatically (see 'workfileCompositing' item in `Subsets to publish` column.) This allows to publish (and therefore backup)
|
||||
workfile which is used to produce another publishable elements (as `image` and `review` items).
|
||||
|
||||
Main publishable item in AfterEffects will be of `render` family. Result of this item (instance) is picture sequence that could be a final delivery product or loaded and used in another DCCs.
|
||||
|
||||
First select existing composition and then press `Create >>>` in middle column of `Publisher`.
|
||||
|
||||
After this process you should have something like this:
|
||||
|
||||

|
||||
|
||||
Name of publishable instance (eg. subset name) could be configured with a template in `project_settings/global/tools/creator/subset_name_profiles`.
|
||||
(This must be configured by admin who has access to Openpype Settings.)
|
||||
|
||||
Trash icon under the list of instances allows to delete any selected `render` instance.
|
||||
|
||||
Workfile instance will be automatically recreated though. If you do not want to publish it, use pill toggle on the instance item.
|
||||
|
||||
If you would like to modify publishable instance, click on `Publish` tab at the top. This would allow you to change name of publishable
|
||||
instances, disable them from publishing, change their task etc.
|
||||
|
||||
Publisher allows publishing into different context, just click on any instance, update `Variant`, `Asset` or `Task` in the form in the middle and don't forget to click on the 'Confirm' button.
|
||||
|
||||
#### RenderQueue
|
||||
|
||||
AE's Render Queue is required for publishing locally or on a farm. Artist needs to configure expected result format (extension, resolution) in the Render Queue in an Output module. Currently its expected to have only single render item and single output module in the Render Queue.
|
||||
|
||||
AE might throw some warning windows during publishing locally, so please pay attention to them in a case publishing seems to be stuck in a `Extract Local Render`.
|
||||
|
||||
When you are ready to share your work, you will need to publish it. This is done by opening the `Publish` by clicking the corresponding button in the OpenPype Panel.
|
||||
#### Repair Validation Issues
|
||||
|
||||

|
||||
If you would like to run validation rules set by your Studio, click on funnel icon at the bottom right. This will run through all
|
||||
enabled instances, you could see more information after clicking on `Details` tab.
|
||||
|
||||
This tool will run through checks to make sure the contents you are publishing is correct. Hit the "Play" button to start publishing.
|
||||
If there is some issue in validator phase, you will receive something like this:
|
||||

|
||||
|
||||
You may encounter issues with publishing which will be indicated with red squares. If these issues are within the validation section, then you can fix the issue. If there are issues outside of validation section, please let the OpenPype team know. For More details have a look at the general [Publish](artist_tools.md#publisher) documentation.
|
||||
All validators will give some description about what the issue is. You can inspect this by clicking on items in the left column.
|
||||
|
||||
If there is an option of automatic repair, there will be `Repair` button on the right. In other case you need to fix the issue manually.
|
||||
(By deleting and recreating instance, changing workfile setting etc.)
|
||||
|
||||
#### Render instance options
|
||||
|
||||
There are currently 2 options of `render` item:
|
||||
- Render of farm - allows offload rendering and publishing to Deadline - requires Deadline module being enabled
|
||||
- Validate Scene Settings - enables validation plugin which controls setting in DB (or asset control system like Ftrak) and scene itself
|
||||
|
||||

|
||||
|
||||
#### Buttons on the bottom right are for:
|
||||
- `Refresh publishing` - set publishing process to starting position - useful if previous publish failed, or you changed configuration of a publish
|
||||
- `Stop/pause publishing` - if you would like to pause publishing process at any time
|
||||
- `Validate` - if you would like to run only collecting and validating phases (nothing will be published yet)
|
||||
- `Publish` - standard way how to kick off full publishing process
|
||||
|
||||
### Load
|
||||
|
||||
|
|
@ -102,12 +135,19 @@ You can switch to a previous version of the image or update to the latest.
|
|||

|
||||

|
||||
|
||||
### Subset Manager
|
||||
#### Support help
|
||||
If you would like to ask for help admin or support, you could use any of the three options on the `Note` button on bottom left:
|
||||
- `Go to details` - switches into a more detailed list of published instances and plugins.
|
||||
- `Copy report` - stash full publishing log to a clipboard
|
||||
- `Export report` - save log into a file for sending it via mail or any communication tool
|
||||
|
||||

|
||||
If you are able to fix the workfile yourself, use the first button on the right to set the UI to initial state before publish. (Click the `Publish` button to start again.)
|
||||
|
||||
All created compositions will be shown in a simple list. If user decides, that this composition shouldn't be
|
||||
published after all, right click on that item in the list and select 'Remove instance'
|
||||
#### Legacy instances
|
||||
|
||||
Removing composition directly in the AE would result to worfile contain phantom metadata which could result in
|
||||
errors during publishing!
|
||||
All screenshots from Publish are from updated dialog, before publishing was being done by regular `Pyblish` tool.
|
||||
New publishing process should be backward compatible, eg. if you have a workfile with instances created in the previous publishing approach, they will be translated automatically and
|
||||
could be used right away.
|
||||
|
||||
If you hit on unexpected behaviour with old instances, contact support first, then you could try to delete and recreate instances from scratch.
|
||||
Nuclear option is to purge workfile metadata in `Window > Metadata > Basic > Label`. This is only for most determined daredevils though!
|
||||
|
|
@ -22,32 +22,75 @@ When you launch Photoshop you will be met with the Workfiles app. If dont have a
|
|||
|
||||
In Photoshop you can find the tools in the `OpenPype` extension:
|
||||
|
||||
 <!-- picture needs to be changed -->
|
||||
 <!-- picture needs to be changed -->
|
||||
|
||||
You can show the extension panel by going to `Window` > `Extensions` > `OpenPype`.
|
||||
|
||||
### Create
|
||||
### Publish
|
||||
|
||||
When you have created an image you want to publish, you will need to create special groups or tag existing groups. To do this open the `Creator` through the extensions `Create` button.
|
||||
When you are ready to share some work, you will need to publish. This is done by opening the `Publisher` through the `Publish...` button.
|
||||
|
||||

|
||||

|
||||
|
||||
With the `Creator` you have a variety of options to create:
|
||||
There is always instance for workfile created automatically (see 'workfileArt' item in `Subsets to publish` column.) This allows to publish (and therefore backup)
|
||||
workfile which is used to produce another publishable elements (as `image` and `review` items).
|
||||
|
||||
- Check `Use selection` (A dialog will ask whether you want to create one image per selected layer).
|
||||
- Yes.
|
||||
- No selection.
|
||||
- This will create a single group named after the `Subset` in the `Creator`.
|
||||
- Single selected layer.
|
||||
- The selected layer will be grouped under a single group named after the selected layer.
|
||||
- Single selected group.
|
||||
- The selected group will be tagged for publishing.
|
||||
- Multiple selected items.
|
||||
- Each selected group will be tagged for publishing and each layer will be grouped individually.
|
||||
- No.
|
||||
- All selected layers will be grouped under a single group named after the `Subset` in the `Creator`.
|
||||
- Uncheck `Use selection`.
|
||||
- This will create a single group named after the `Subset` in the `Creator`.
|
||||
#### Create
|
||||
|
||||
Main publishable item in Photoshop will be of `image` family. Result of this item (instance) is picture that could be loaded and used in another DCCs (for example as
|
||||
single layer in composition in AfterEffects, reference in Maya etc).
|
||||
|
||||
There are couple of options what to publish:
|
||||
- separate image per layer (or group of layers)
|
||||
- all visible layers (groups) flattened into single image
|
||||
|
||||
In most cases you would like to keep `Create only for selected` toggled on and select what you would like to publish. Toggling this off
|
||||
will allow you to create instance(s) for all visible layers without a need to select them explicitly.
|
||||
|
||||
For separate layers option keep `Create separate instance for each selected` toggled, select multiple layers and hit `Create >>>` button in the middle column.
|
||||
|
||||
This will result in:
|
||||
|
||||

|
||||
|
||||
(In Photoshop's `Layers` tab standard layers will be wrapped into group and enriched with ℗ symbol to denote publishable instance. With `Create separate instance for each selected` toggled off
|
||||
it will create only single publishable instance which will wrap all visible layers.)
|
||||
|
||||
Name of publishable instance (eg. subset name) could be configured with a template in `project_settings/global/tools/creator/subset_name_profiles`.
|
||||
(This must be configured by admin who has access to Openpype Settings.)
|
||||
|
||||
Trash icon under the list of instances allows to delete any selected `image` instance.
|
||||
|
||||
Workfile instance will be automatically recreated though. If you do not want to publish it, use pill toggle on the instance item.
|
||||
|
||||
If you would like to modify publishable instance, click on `Publish` tab at the top. This would allow you to change name of publishable
|
||||
instances, disable them from publishing, change their task etc.
|
||||
|
||||
Publisher allows publishing into different context, just click on any instance, update `Variant`, `Asset` or `Task` in the form in the middle and don't forget to click on the 'Confirm' button.
|
||||
|
||||
#### Validate
|
||||
|
||||
If you would like to run validation rules set by your Studio, click on funnel icon at the bottom right. This will run through all
|
||||
enabled instances, you could see more information after clicking on `Details` tab.
|
||||
|
||||

|
||||
|
||||
In this dialog you could see publishable instances in left colummn, triggered plugins in the middle and logs in the right column.
|
||||
|
||||
In left column you could see that `review` instance was created automatically. This instance flattens all publishable instances or
|
||||
all visible layers if no publishable instances were created into single image which could serve as a single reviewable element (for example in Ftrack).
|
||||
|
||||
Creation of Review could be disabled in `project_settings/photoshop/publish/CollectReview`.
|
||||
|
||||
If you are satisfied with results of validation phase (and there are no errors there), you might hit `Publish` button at bottom right.
|
||||
This will run through extraction phase (it physically creates images from `image` instances, creates `review` etc) and publishes them
|
||||
(eg. stores files into their final destination and stores metadata about them into DB).
|
||||
This part might take a while depending on amount of layers in the workfile, amount of available memory and performance of your machine.
|
||||
|
||||
You may encounter issues with publishing which will be indicated with red squares. If these issues are within the validation section, then you can fix the issue. If there are issues outside of validation section, please let the OpenPype team know.
|
||||
|
||||
You can always start new publish run with a circle arrow button at the bottom right. You might also want to move between phases (Create, Update etc)
|
||||
by clicking on available tabs at the top of the dialog.
|
||||
|
||||
#### Simplified publish
|
||||
|
||||
|
|
@ -55,39 +98,28 @@ There is a simplified workflow for simple use case where only single image shoul
|
|||
No image instances must be present in a workfile and `project_settings/photoshop/publish/CollectInstances/flatten_subset_template` must be filled in Settings.
|
||||
Then artists just need to hit 'Publish' button in menu.
|
||||
|
||||
### Publish
|
||||
|
||||
When you are ready to share some work, you will need to publish. This is done by opening the `Pyblish` through the extensions `Publish` button.
|
||||
|
||||
 <!-- picture needs to be changed -->
|
||||
|
||||
This tool will run through checks to make sure the contents you are publishing is correct. Hit the "Play" button to start publishing.
|
||||
|
||||
You may encounter issues with publishing which will be indicated with red squares. If these issues are within the validation section, then you can fix the issue. If there are issues outside of validation section, please let the OpenPype team know.
|
||||
|
||||
#### Repair Validation Issues
|
||||
|
||||
All validators will give some description about what the issue is. You can inspect this by going into the validator through the arrow:
|
||||
If there is some issue in validator phase, you will receive something like this:
|
||||
|
||||
 <!-- picture needs to be changed -->
|
||||

|
||||
|
||||
You can expand the errors by clicking on them for more details:
|
||||
All validators will give some description about what the issue is. You can inspect this by clicking on items in the left column.
|
||||
|
||||
 <!-- picture needs to be changed -->
|
||||
If there is an option of automatic repair, there will be `Repair` button on the right. In other case you need to fix the issue manually.
|
||||
(By deleting and recreating instance etc.)
|
||||
|
||||
Some validator have repair actions, which will fix the issue. If you can identify validators with actions by the circle icon with an "A":
|
||||
|
||||
 <!-- picture needs to be changed -->
|
||||
|
||||
To access the actions, you right click on the validator. If an action runs successfully, the actions icon will turn green. Once all issues are fixed, you can just hit the "Refresh" button and try to publish again.
|
||||
|
||||
 <!-- picture needs to be changed -->
|
||||
#### Buttons on the bottom right are for:
|
||||
- `Refresh publishing` - set publishing process to starting position - useful if previous publish failed, or you changed configuration of a publish
|
||||
- `Stop/pause publishing` - if you would like to pause publishing process at any time
|
||||
- `Validate` - if you would like to run only collecting and validating phases (nothing will be published yet)
|
||||
- `Publish` - standard way how to kick off full publishing process
|
||||
|
||||
### Load
|
||||
|
||||
When you want to load existing published work, you can load in smart layers through the `Loader`. You can reach the `Loader` through the extension's `Load` button.
|
||||
|
||||
 <!-- picture needs to be changed -->
|
||||
 <!-- picture needs to be changed -->
|
||||
|
||||
The supported families for Photoshop are:
|
||||
|
||||
|
|
@ -105,7 +137,7 @@ Now that we have some images loaded, we can manage which version is loaded. This
|
|||
Loaded images has to stay as smart layers in order to be updated. If you rasterize the layer, you cannot update it to a different version.
|
||||
:::
|
||||
|
||||

|
||||

|
||||
|
||||
You can switch to a previous version of the image or update to the latest.
|
||||
|
||||
|
|
@ -113,65 +145,19 @@ You can switch to a previous version of the image or update to the latest.
|
|||

|
||||
|
||||
|
||||
### New Publisher
|
||||
|
||||
All previous screenshot came from regular [pyblish](https://pyblish.com/) process, there is also a different UI available. This process extends existing implementation and adds new functionalities.
|
||||
|
||||
To test this in Photoshop, the artist needs first to enable experimental `New publisher` in Settings. (Tray > Settings > Experimental tools)
|
||||

|
||||
|
||||
New dialog opens after clicking on `Experimental tools` button in Openpype extension menu.
|
||||

|
||||
|
||||
After you click on this button, this dialog will show up.
|
||||
|
||||

|
||||
|
||||
You can see the first instance, called `workfileYourTaskName`. (Name depends on studio naming convention for Photoshop's workfiles.). This instance is so called "automatic",
|
||||
it was created without instigation by the artist. You shouldn't delete this instance as it might hold necessary values for future publishing, but you can choose to skip it
|
||||
from publishing (by toggling the pill button inside of the rectangular object denoting instance).
|
||||
|
||||
New publisher allows publishing into different context, just click on a workfile instance, update `Variant`, `Asset` or `Task` in the form in the middle and don't forget to click on the 'Confirm' button.
|
||||
|
||||
Similarly to the old publishing approach, you need to create instances for everything you want to publish. You will initiate by clicking on the '+' sign in the bottom left corner.
|
||||
|
||||

|
||||
|
||||
In this dialog you can select the family for the published layer or group. Currently only 'image' is implemented.
|
||||
|
||||
On right hand side you can see creator attributes:
|
||||
- `Create only for selected` - mimics `Use selected` option of regular publish
|
||||
- `Create separate instance for each selected` - if separate instance should be created for each layer if multiple selected
|
||||
|
||||

|
||||
|
||||
Here you can see a newly created instance of image family. (Name depends on studio naming convention for image family.) You can disable instance from publishing in the same fashion as a workfile instance.
|
||||
You could also decide delete instance by selecting it and clicking on a trashcan icon (next to plus button on left button)
|
||||
|
||||
Buttons on the bottom right are for:
|
||||
- `Refresh publishing` - set publishing process to starting position - useful if previous publish failed, or you changed configuration of a publish
|
||||
- `Stop/pause publishing` - if you would like to pause publishing process at any time
|
||||
- `Validate` - if you would like to run only collecting and validating phases (nothing will be published yet)
|
||||
- `Publish` - standard way how to kick off full publishing process
|
||||
|
||||
In the unfortunate case of some error during publishing, you would receive this kind of error dialog.
|
||||
|
||||

|
||||
|
||||
In this case there is an issue that you are publishing two or more instances with the same subset name ('imageMaing'). If the error is recoverable by the artist, you should
|
||||
see helpful information in a `How to repair?` section or fix it automatically by clicking on a 'Wrench' button on the right if present.
|
||||
|
||||
If you would like to ask for help admin or support, you could use any of the three buttons on bottom left:
|
||||
#### Support help
|
||||
If you would like to ask for help admin or support, you could use any of the three options on the `Note` button on bottom left:
|
||||
- `Go to details` - switches into a more detailed list of published instances and plugins.
|
||||
- `Copy report` - stash full publishing log to a clipboard
|
||||
- `Export and save report` - save log into a file for sending it via mail or any communication tool
|
||||
- `Show details` - switches into a more detailed list of published instances and plugins. Similar to the old pyblish list.
|
||||
- `Export report` - save log into a file for sending it via mail or any communication tool
|
||||
|
||||
If you are able to fix the workfile yourself, use the first button on the right to set the UI to initial state before publish. (Click the `Publish` button to start again.)
|
||||
|
||||
#### Legacy instances
|
||||
|
||||
All screenshots from Publish are from updated dialog, before publishing was being done by regular `Pyblish` tool.
|
||||
New publishing process should be backward compatible, eg. if you have a workfile with instances created in the previous publishing approach, they will be translated automatically and
|
||||
could be used right away.
|
||||
|
||||
If you would create instances in a new publisher, you cannot use them in the old approach though!
|
||||
|
||||
If you would hit on unexpected behaviour with old instances, contact support first, then you could try some steps to recover your publish. Delete instances in New publisher UI, or try `Subset manager` in the extension menu.
|
||||
If you hit on unexpected behaviour with old instances, contact support first, then you could try to delete and recreate instances from scratch.
|
||||
Nuclear option is to purge workfile metadata in `File > File Info > Origin > Headline`. This is only for most determined daredevils though!
|
||||
|
|
|
|||
BIN
website/docs/assets/aftereffects_publish_failed.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
website/docs/assets/aftereffects_publish_instance.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
website/docs/assets/aftereffects_render_instance.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
BIN
website/docs/assets/photoshop_extension.png
Normal file
|
After Width: | Height: | Size: 8 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 19 KiB |
BIN
website/docs/assets/photoshop_publish.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 35 KiB |
BIN
website/docs/assets/photoshop_publish_failed.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
website/docs/assets/photoshop_publish_images.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 937 KiB |
BIN
website/docs/assets/photoshop_publish_validations.png
Normal file
|
After Width: | Height: | Size: 305 KiB |
|
|
@ -11,7 +11,7 @@ import TabItem from '@theme/TabItem';
|
|||
|
||||
To build Pype you currently need (on all platforms):
|
||||
|
||||
- **[Python 3.7](https://www.python.org/downloads/)** as we are following [vfx platform](https://vfxplatform.com).
|
||||
- **[Python 3.9](https://www.python.org/downloads/)** as we are following [vfx platform CY2022](https://vfxplatform.com).
|
||||
- **[git](https://git-scm.com/downloads)**
|
||||
|
||||
We use [CX_Freeze](https://cx-freeze.readthedocs.io/en/latest) to freeze the code and all dependencies and
|
||||
|
|
@ -114,8 +114,8 @@ To build OpenPype on Linux you will need:
|
|||
- **[curl](https://curl.se)** on systems that doesn't have one preinstalled.
|
||||
- **bzip2**, **readline**, **sqlite3** and other libraries.
|
||||
|
||||
Because some Linux distros come with newer Python version pre-installed, you might
|
||||
need to install **3.7** version and make use of it explicitly.
|
||||
Because some Linux distros come with older Python version pre-installed, you might
|
||||
need to install **3.9** version and make use of it explicitly.
|
||||
Your best bet is probably using [pyenv](https://github.com/pyenv/pyenv).
|
||||
|
||||
You can use your package manager to install **git** and other packages to your build
|
||||
|
|
@ -136,16 +136,16 @@ $ eval "$(pyenv virtualenv-init -)"
|
|||
# reload shell
|
||||
$ exec $SHELL
|
||||
|
||||
# install Python 3.7.10
|
||||
# install Python 3.9.6
|
||||
# python will be downloaded and build so please make sure
|
||||
# you have all necessary requirements installed (see below).
|
||||
$ pyenv install -v 3.7.10
|
||||
$ pyenv install -v 3.9.6
|
||||
|
||||
# change path to pype 3
|
||||
$ cd /path/to/pype-3
|
||||
|
||||
# set local python version
|
||||
$ pyenv local 3.7.10
|
||||
$ pyenv local 3.9.6
|
||||
```
|
||||
:::note Install build requirements for **Ubuntu**
|
||||
|
||||
|
|
@ -220,19 +220,19 @@ $ exec "$SHELL"
|
|||
$ PATH=$(pyenv root)/shims:$PATH
|
||||
```
|
||||
|
||||
4) Pull in required Python version 3.7.x
|
||||
4) Pull in required Python version 3.9.x
|
||||
```shell
|
||||
# install Python build dependences
|
||||
$ brew install openssl readline sqlite3 xz zlib
|
||||
|
||||
# replace with up-to-date 3.7.x version
|
||||
$ pyenv install 3.7.9
|
||||
# replace with up-to-date 3.9.x version
|
||||
$ pyenv install 3.9.6
|
||||
```
|
||||
|
||||
5) Set local Python version
|
||||
```shell
|
||||
# switch to Pype source directory
|
||||
$ pyenv local 3.7.9
|
||||
$ pyenv local 3.9.6
|
||||
```
|
||||
|
||||
6) Install `create-dmg`
|
||||
|
|
@ -256,7 +256,7 @@ to `pyproject.toml` to `[tool.poetry.dependencies]` section.
|
|||
|
||||
```toml title="/pyproject.toml"
|
||||
[tool.poetry.dependencies]
|
||||
python = "3.7.*"
|
||||
python = "3.9.*"
|
||||
aiohttp = "^3.7"
|
||||
aiohttp_json_rpc = "*" # TVPaint server
|
||||
acre = { git = "https://github.com/pypeclub/acre.git" }
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ The main things you will need to run and build pype are:
|
|||
- **Terminal** in your OS
|
||||
- PowerShell 5.0+ (Windows)
|
||||
- Bash (Linux)
|
||||
- [**Python 3.7.9**](#python) or higher
|
||||
- [**Python 3.9.x**](#python)
|
||||
- [**MongoDB**](#database)
|
||||
|
||||
|
||||
|
|
@ -55,13 +55,14 @@ To run mongoDB on server, use your server distribution tools to set it up (on Li
|
|||
|
||||
## Python
|
||||
|
||||
**Python 3.7.8** is the recommended version to use (as per [VFX platform CY2021](https://vfxplatform.com/)).
|
||||
**Python 3.9.x** is the recommended version to use (as per [VFX platform CY2022](https://vfxplatform.com/)).
|
||||
**Note**: We do not support 3.9.0 because of [this bug](https://github.com/python/cpython/pull/22670). Please, use higher versions of 3.9.x.
|
||||
|
||||
If you're planning to run openPYPE on workstations from built executables (highly recommended), you will only need python for building and development, however, if you'd like to run from source centrally, every user will need python installed.
|
||||
If you're planning to run openPYPE on workstations from built executables (highly recommended), you will only need python for building and development, however, if you'd like to run from source centrally, every user will need python installed.
|
||||
|
||||
## Hardware
|
||||
|
||||
openPYPE should be installed on all workstations that need to use it, the same as any other application.
|
||||
openPYPE should be installed on all workstations that need to use it, the same as any other application.
|
||||
|
||||
There are no specific requirements for the hardware. If the workstation can run
|
||||
the major DCCs, it most probably can run openPYPE.
|
||||
|
|
|
|||