mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-02 00:44:52 +01:00
Merge branch 'develop' into feature/PYPE-1120-nk-geo-workflow
This commit is contained in:
commit
48a11f5873
86 changed files with 1799 additions and 1357 deletions
|
|
@ -87,7 +87,7 @@ ipython_config.py
|
|||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
|
|
@ -142,5 +142,6 @@ cython_debug/
|
|||
.poetry/
|
||||
.github/
|
||||
vendor/bin/
|
||||
vendor/python/
|
||||
docs/
|
||||
website/
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -39,6 +39,7 @@ Temporary Items
|
|||
/dist/
|
||||
|
||||
/vendor/bin/*
|
||||
/vendor/python/*
|
||||
/.venv
|
||||
/venv/
|
||||
|
||||
|
|
|
|||
38
CHANGELOG.md
38
CHANGELOG.md
|
|
@ -1,8 +1,8 @@
|
|||
# Changelog
|
||||
|
||||
## [3.5.0-nightly.5](https://github.com/pypeclub/OpenPype/tree/HEAD)
|
||||
## [3.5.0](https://github.com/pypeclub/OpenPype/tree/3.5.0) (2021-10-17)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.4.1...HEAD)
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.4.1...3.5.0)
|
||||
|
||||
**Deprecated:**
|
||||
|
||||
|
|
@ -10,12 +10,18 @@
|
|||
|
||||
**🆕 New features**
|
||||
|
||||
- Added project and task into context change message in Maya [\#2131](https://github.com/pypeclub/OpenPype/pull/2131)
|
||||
- Add ExtractBurnin to photoshop review [\#2124](https://github.com/pypeclub/OpenPype/pull/2124)
|
||||
- PYPE-1218 - changed namespace to contain subset name in Maya [\#2114](https://github.com/pypeclub/OpenPype/pull/2114)
|
||||
- Added running configurable disk mapping command before start of OP [\#2091](https://github.com/pypeclub/OpenPype/pull/2091)
|
||||
- SFTP provider [\#2073](https://github.com/pypeclub/OpenPype/pull/2073)
|
||||
- Maya: Validate setdress top group [\#2068](https://github.com/pypeclub/OpenPype/pull/2068)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
- Maya: make rig validators configurable in settings [\#2137](https://github.com/pypeclub/OpenPype/pull/2137)
|
||||
- Settings: Updated readme for entity types in settings [\#2132](https://github.com/pypeclub/OpenPype/pull/2132)
|
||||
- Nuke: unified clip loader [\#2128](https://github.com/pypeclub/OpenPype/pull/2128)
|
||||
- Settings UI: Project model refreshing and sorting [\#2104](https://github.com/pypeclub/OpenPype/pull/2104)
|
||||
- Create Read From Rendered - Disable Relative paths by default [\#2093](https://github.com/pypeclub/OpenPype/pull/2093)
|
||||
- Added choosing different dirmap mapping if workfile synched locally [\#2088](https://github.com/pypeclub/OpenPype/pull/2088)
|
||||
|
|
@ -28,30 +34,33 @@
|
|||
- Nuke: Adding `still` image family workflow [\#2064](https://github.com/pypeclub/OpenPype/pull/2064)
|
||||
- Maya: validate authorized loaded plugins [\#2062](https://github.com/pypeclub/OpenPype/pull/2062)
|
||||
- Tools: add support for pyenv on windows [\#2051](https://github.com/pypeclub/OpenPype/pull/2051)
|
||||
- SyncServer: Dropbox Provider [\#1979](https://github.com/pypeclub/OpenPype/pull/1979)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Maya: fix model publishing [\#2130](https://github.com/pypeclub/OpenPype/pull/2130)
|
||||
- Fix - oiiotool wasn't recognized even if present [\#2129](https://github.com/pypeclub/OpenPype/pull/2129)
|
||||
- General: Disk mapping group [\#2120](https://github.com/pypeclub/OpenPype/pull/2120)
|
||||
- Hiero: publishing effect first time makes wrong resources path [\#2115](https://github.com/pypeclub/OpenPype/pull/2115)
|
||||
- Add startup script for Houdini Core. [\#2110](https://github.com/pypeclub/OpenPype/pull/2110)
|
||||
- TVPaint: Behavior name of loop also accept repeat [\#2109](https://github.com/pypeclub/OpenPype/pull/2109)
|
||||
- Ftrack: Project settings save custom attributes skip unknown attributes [\#2103](https://github.com/pypeclub/OpenPype/pull/2103)
|
||||
- Blender: Fix NoneType error when animation\_data is missing for a rig [\#2101](https://github.com/pypeclub/OpenPype/pull/2101)
|
||||
- Fix broken import in sftp provider [\#2100](https://github.com/pypeclub/OpenPype/pull/2100)
|
||||
- Global: Fix docstring on publish plugin extract review [\#2097](https://github.com/pypeclub/OpenPype/pull/2097)
|
||||
- Delivery Action Files Sequence fix [\#2096](https://github.com/pypeclub/OpenPype/pull/2096)
|
||||
- General: Cloud mongo ca certificate issue [\#2095](https://github.com/pypeclub/OpenPype/pull/2095)
|
||||
- TVPaint: Creator use context from workfile [\#2087](https://github.com/pypeclub/OpenPype/pull/2087)
|
||||
- Blender: fix texture missing when publishing blend files [\#2085](https://github.com/pypeclub/OpenPype/pull/2085)
|
||||
- General: Startup validations oiio tool path fix on linux [\#2083](https://github.com/pypeclub/OpenPype/pull/2083)
|
||||
- Deadline: Collect deadline server does not check existence of deadline key [\#2082](https://github.com/pypeclub/OpenPype/pull/2082)
|
||||
- Blender: fixed Curves with modifiers in Rigs [\#2081](https://github.com/pypeclub/OpenPype/pull/2081)
|
||||
- Nuke UI scaling [\#2077](https://github.com/pypeclub/OpenPype/pull/2077)
|
||||
- Maya: Fix multi-camera renders [\#2065](https://github.com/pypeclub/OpenPype/pull/2065)
|
||||
- Fix Sync Queue when project disabled [\#2063](https://github.com/pypeclub/OpenPype/pull/2063)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Blender: Fix NoneType error when animation\_data is missing for a rig [\#2101](https://github.com/pypeclub/OpenPype/pull/2101)
|
||||
- Delivery Action Files Sequence fix [\#2096](https://github.com/pypeclub/OpenPype/pull/2096)
|
||||
- Bump pywin32 from 300 to 301 [\#2086](https://github.com/pypeclub/OpenPype/pull/2086)
|
||||
- Nuke UI scaling [\#2077](https://github.com/pypeclub/OpenPype/pull/2077)
|
||||
|
||||
## [3.4.1](https://github.com/pypeclub/OpenPype/tree/3.4.1) (2021-09-23)
|
||||
|
||||
|
|
@ -92,10 +101,6 @@
|
|||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.4.0-nightly.6...3.4.0)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Documentation: Ftrack launch argsuments update [\#2014](https://github.com/pypeclub/OpenPype/pull/2014)
|
||||
|
||||
**🆕 New features**
|
||||
|
||||
- Nuke: Compatibility with Nuke 13 [\#2003](https://github.com/pypeclub/OpenPype/pull/2003)
|
||||
|
|
@ -111,10 +116,6 @@
|
|||
- Modules: Connect method is not required [\#2009](https://github.com/pypeclub/OpenPype/pull/2009)
|
||||
- Settings UI: Number with configurable steps [\#2001](https://github.com/pypeclub/OpenPype/pull/2001)
|
||||
- Moving project folder structure creation out of ftrack module \#1989 [\#1996](https://github.com/pypeclub/OpenPype/pull/1996)
|
||||
- Configurable items for providers without Settings [\#1987](https://github.com/pypeclub/OpenPype/pull/1987)
|
||||
- Global: Example addons [\#1986](https://github.com/pypeclub/OpenPype/pull/1986)
|
||||
- Standalone Publisher: Extract harmony zip handle workfile template [\#1982](https://github.com/pypeclub/OpenPype/pull/1982)
|
||||
- Settings UI: Number sliders [\#1978](https://github.com/pypeclub/OpenPype/pull/1978)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
|
|
@ -125,11 +126,10 @@
|
|||
- FFmpeg: Subprocess arguments as list [\#2032](https://github.com/pypeclub/OpenPype/pull/2032)
|
||||
- General: Fix Python 2 breaking line [\#2016](https://github.com/pypeclub/OpenPype/pull/2016)
|
||||
- Bugfix/webpublisher task type [\#2006](https://github.com/pypeclub/OpenPype/pull/2006)
|
||||
- Nuke thumbnails generated from middle of the sequence [\#1992](https://github.com/pypeclub/OpenPype/pull/1992)
|
||||
- Nuke: last version from path gets correct version [\#1990](https://github.com/pypeclub/OpenPype/pull/1990)
|
||||
- nuke, resolve, hiero: precollector order lest then 0.5 [\#1984](https://github.com/pypeclub/OpenPype/pull/1984)
|
||||
- Last workfile with multiple work templates [\#1981](https://github.com/pypeclub/OpenPype/pull/1981)
|
||||
- Collectors order [\#1977](https://github.com/pypeclub/OpenPype/pull/1977)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Documentation: Ftrack launch argsuments update [\#2014](https://github.com/pypeclub/OpenPype/pull/2014)
|
||||
|
||||
## [3.3.1](https://github.com/pypeclub/OpenPype/tree/3.3.1) (2021-08-20)
|
||||
|
||||
|
|
|
|||
83
Dockerfile
83
Dockerfile
|
|
@ -1,7 +1,9 @@
|
|||
# Build Pype docker image
|
||||
FROM centos:7 AS builder
|
||||
ARG OPENPYPE_PYTHON_VERSION=3.7.10
|
||||
FROM debian:bookworm-slim AS builder
|
||||
ARG OPENPYPE_PYTHON_VERSION=3.7.12
|
||||
|
||||
LABEL maintainer="info@openpype.io"
|
||||
LABEL description="Docker Image to build and run OpenPype"
|
||||
LABEL org.opencontainers.image.name="pypeclub/openpype"
|
||||
LABEL org.opencontainers.image.title="OpenPype Docker Image"
|
||||
LABEL org.opencontainers.image.url="https://openpype.io/"
|
||||
|
|
@ -9,56 +11,49 @@ LABEL org.opencontainers.image.source="https://github.com/pypeclub/pype"
|
|||
|
||||
USER root
|
||||
|
||||
# update base
|
||||
RUN yum -y install deltarpm \
|
||||
&& yum -y update \
|
||||
&& yum clean all
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# add tools we need
|
||||
RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm \
|
||||
&& yum -y install centos-release-scl \
|
||||
&& yum -y install \
|
||||
# update base
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
bash \
|
||||
which \
|
||||
git \
|
||||
devtoolset-7-gcc* \
|
||||
make \
|
||||
cmake \
|
||||
make \
|
||||
curl \
|
||||
wget \
|
||||
gcc \
|
||||
zlib-devel \
|
||||
bzip2 \
|
||||
bzip2-devel \
|
||||
readline-devel \
|
||||
sqlite sqlite-devel \
|
||||
openssl-devel \
|
||||
tk-devel libffi-devel \
|
||||
qt5-qtbase-devel \
|
||||
patchelf \
|
||||
&& yum clean all
|
||||
build-essential \
|
||||
checkinstall \
|
||||
libssl-dev \
|
||||
zlib1g-dev \
|
||||
libbz2-dev \
|
||||
libreadline-dev \
|
||||
libsqlite3-dev \
|
||||
llvm \
|
||||
libncursesw5-dev \
|
||||
xz-utils \
|
||||
tk-dev \
|
||||
libxml2-dev \
|
||||
libxmlsec1-dev \
|
||||
libffi-dev \
|
||||
liblzma-dev \
|
||||
patchelf
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
RUN mkdir /opt/openpype
|
||||
# RUN useradd -m pype
|
||||
# RUN chown pype /opt/openpype
|
||||
# USER pype
|
||||
|
||||
RUN curl https://pyenv.run | bash
|
||||
ENV PYTHON_CONFIGURE_OPTS --enable-shared
|
||||
|
||||
RUN echo 'export PATH="$HOME/.pyenv/bin:$PATH"'>> $HOME/.bashrc \
|
||||
RUN curl https://pyenv.run | bash \
|
||||
&& echo 'export PATH="$HOME/.pyenv/bin:$PATH"'>> $HOME/.bashrc \
|
||||
&& echo 'eval "$(pyenv init -)"' >> $HOME/.bashrc \
|
||||
&& echo 'eval "$(pyenv virtualenv-init -)"' >> $HOME/.bashrc \
|
||||
&& echo 'eval "$(pyenv init --path)"' >> $HOME/.bashrc
|
||||
RUN source $HOME/.bashrc && pyenv install ${OPENPYPE_PYTHON_VERSION}
|
||||
&& echo 'eval "$(pyenv init --path)"' >> $HOME/.bashrc \
|
||||
&& source $HOME/.bashrc && pyenv install ${OPENPYPE_PYTHON_VERSION}
|
||||
|
||||
COPY . /opt/openpype/
|
||||
RUN rm -rf /openpype/.poetry || echo "No Poetry installed yet."
|
||||
# USER root
|
||||
# RUN chown -R pype /opt/openpype
|
||||
RUN chmod +x /opt/openpype/tools/create_env.sh && chmod +x /opt/openpype/tools/build.sh
|
||||
|
||||
# USER pype
|
||||
RUN chmod +x /opt/openpype/tools/create_env.sh && chmod +x /opt/openpype/tools/build.sh
|
||||
|
||||
WORKDIR /opt/openpype
|
||||
|
||||
|
|
@ -67,16 +62,8 @@ RUN cd /opt/openpype \
|
|||
&& pyenv local ${OPENPYPE_PYTHON_VERSION}
|
||||
|
||||
RUN source $HOME/.bashrc \
|
||||
&& ./tools/create_env.sh
|
||||
|
||||
RUN source $HOME/.bashrc \
|
||||
&& ./tools/create_env.sh \
|
||||
&& ./tools/fetch_thirdparty_libs.sh
|
||||
|
||||
RUN source $HOME/.bashrc \
|
||||
&& bash ./tools/build.sh \
|
||||
&& 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
|
||||
|
||||
RUN cd /opt/openpype \
|
||||
rm -rf ./vendor/bin
|
||||
&& bash ./tools/build.sh
|
||||
|
|
|
|||
98
Dockerfile.centos7
Normal file
98
Dockerfile.centos7
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# Build Pype docker image
|
||||
FROM centos:7 AS builder
|
||||
ARG OPENPYPE_PYTHON_VERSION=3.7.10
|
||||
|
||||
LABEL org.opencontainers.image.name="pypeclub/openpype"
|
||||
LABEL org.opencontainers.image.title="OpenPype Docker Image"
|
||||
LABEL org.opencontainers.image.url="https://openpype.io/"
|
||||
LABEL org.opencontainers.image.source="https://github.com/pypeclub/pype"
|
||||
|
||||
USER root
|
||||
|
||||
# update base
|
||||
RUN yum -y install deltarpm \
|
||||
&& yum -y update \
|
||||
&& yum clean all
|
||||
|
||||
# add tools we need
|
||||
RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm \
|
||||
&& yum -y install centos-release-scl \
|
||||
&& yum -y install \
|
||||
bash \
|
||||
which \
|
||||
git \
|
||||
make \
|
||||
devtoolset-7 \
|
||||
cmake \
|
||||
curl \
|
||||
wget \
|
||||
gcc \
|
||||
zlib-devel \
|
||||
bzip2 \
|
||||
bzip2-devel \
|
||||
readline-devel \
|
||||
sqlite sqlite-devel \
|
||||
openssl-devel \
|
||||
openssl-libs \
|
||||
tk-devel libffi-devel \
|
||||
patchelf \
|
||||
automake \
|
||||
autoconf \
|
||||
ncurses \
|
||||
ncurses-devel \
|
||||
qt5-qtbase-devel \
|
||||
&& yum clean all
|
||||
|
||||
# we need to build our own patchelf
|
||||
WORKDIR /temp-patchelf
|
||||
RUN git clone https://github.com/NixOS/patchelf.git . \
|
||||
&& source scl_source enable devtoolset-7 \
|
||||
&& ./bootstrap.sh \
|
||||
&& ./configure \
|
||||
&& make \
|
||||
&& make install
|
||||
|
||||
RUN mkdir /opt/openpype
|
||||
# RUN useradd -m pype
|
||||
# RUN chown pype /opt/openpype
|
||||
# USER pype
|
||||
|
||||
RUN curl https://pyenv.run | bash
|
||||
# ENV PYTHON_CONFIGURE_OPTS --enable-shared
|
||||
|
||||
RUN echo 'export PATH="$HOME/.pyenv/bin:$PATH"'>> $HOME/.bashrc \
|
||||
&& echo 'eval "$(pyenv init -)"' >> $HOME/.bashrc \
|
||||
&& echo 'eval "$(pyenv virtualenv-init -)"' >> $HOME/.bashrc \
|
||||
&& echo 'eval "$(pyenv init --path)"' >> $HOME/.bashrc
|
||||
RUN source $HOME/.bashrc && pyenv install ${OPENPYPE_PYTHON_VERSION}
|
||||
|
||||
COPY . /opt/openpype/
|
||||
RUN rm -rf /openpype/.poetry || echo "No Poetry installed yet."
|
||||
# USER root
|
||||
# RUN chown -R pype /opt/openpype
|
||||
RUN chmod +x /opt/openpype/tools/create_env.sh && chmod +x /opt/openpype/tools/build.sh
|
||||
|
||||
# USER pype
|
||||
|
||||
WORKDIR /opt/openpype
|
||||
|
||||
RUN cd /opt/openpype \
|
||||
&& source $HOME/.bashrc \
|
||||
&& pyenv local ${OPENPYPE_PYTHON_VERSION}
|
||||
|
||||
RUN source $HOME/.bashrc \
|
||||
&& ./tools/create_env.sh
|
||||
|
||||
RUN source $HOME/.bashrc \
|
||||
&& ./tools/fetch_thirdparty_libs.sh
|
||||
|
||||
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
|
||||
|
||||
RUN cd /opt/openpype \
|
||||
rm -rf ./vendor/bin
|
||||
11
README.md
11
README.md
|
|
@ -133,6 +133,12 @@ Easiest way to build OpenPype on Linux is using [Docker](https://www.docker.com/
|
|||
sudo ./tools/docker_build.sh
|
||||
```
|
||||
|
||||
This will by default use Debian as base image. If you need to make Centos 7 compatible build, please run:
|
||||
|
||||
```sh
|
||||
sudo ./tools/docker_build.sh centos7
|
||||
```
|
||||
|
||||
If all is successful, you'll find built OpenPype in `./build/` folder.
|
||||
|
||||
#### Manual build
|
||||
|
|
@ -158,6 +164,11 @@ you'll need also additional libraries for Qt5:
|
|||
```sh
|
||||
sudo apt install qt5-default
|
||||
```
|
||||
or if you are on Ubuntu > 20.04, there is no `qt5-default` packages so you need to install its content individually:
|
||||
|
||||
```sh
|
||||
sudo apt-get install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ def install():
|
|||
"""Install Pype to Avalon."""
|
||||
from pyblish.lib import MessageHandler
|
||||
from openpype.modules import load_modules
|
||||
from avalon import pipeline
|
||||
|
||||
# Make sure modules are loaded
|
||||
load_modules()
|
||||
|
|
@ -117,7 +118,9 @@ def install():
|
|||
|
||||
# apply monkey patched discover to original one
|
||||
log.info("Patching discovery")
|
||||
|
||||
avalon.discover = patched_discover
|
||||
pipeline.discover = patched_discover
|
||||
|
||||
avalon.on("taskChanged", _on_task_change)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import copy
|
|||
import argparse
|
||||
|
||||
from avalon import io
|
||||
from avalon.tools import publish
|
||||
|
||||
import pyblish.api
|
||||
import pyblish.util
|
||||
|
|
@ -13,6 +12,7 @@ from openpype.api import Logger
|
|||
import openpype
|
||||
import openpype.hosts.celaction
|
||||
from openpype.hosts.celaction import api as celaction
|
||||
from openpype.tools.utils import host_tools
|
||||
|
||||
log = Logger().get_logger("Celaction_cli_publisher")
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ def main():
|
|||
|
||||
pyblish.api.register_host(publish_host)
|
||||
|
||||
return publish.show()
|
||||
return host_tools.show_publish()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
from .pipeline import (
|
||||
install,
|
||||
uninstall,
|
||||
publish,
|
||||
launch_workfiles_app
|
||||
uninstall
|
||||
)
|
||||
|
||||
from .utils import (
|
||||
|
|
@ -22,12 +20,9 @@ __all__ = [
|
|||
# pipeline
|
||||
"install",
|
||||
"uninstall",
|
||||
"publish",
|
||||
"launch_workfiles_app",
|
||||
|
||||
# utils
|
||||
"setup",
|
||||
"get_resolve_module",
|
||||
|
||||
# lib
|
||||
"get_additional_data",
|
||||
|
|
|
|||
|
|
@ -3,19 +3,7 @@ import sys
|
|||
|
||||
from Qt import QtWidgets, QtCore
|
||||
|
||||
from .pipeline import (
|
||||
publish,
|
||||
launch_workfiles_app
|
||||
)
|
||||
|
||||
from avalon.tools import (
|
||||
creator,
|
||||
sceneinventory,
|
||||
)
|
||||
from openpype.tools import (
|
||||
loader,
|
||||
libraryloader
|
||||
)
|
||||
from openpype.tools.utils import host_tools
|
||||
|
||||
from openpype.hosts.fusion.scripts import (
|
||||
set_rendermode,
|
||||
|
|
@ -36,7 +24,7 @@ def load_stylesheet():
|
|||
|
||||
class Spacer(QtWidgets.QWidget):
|
||||
def __init__(self, height, *args, **kwargs):
|
||||
super(self.__class__, self).__init__(*args, **kwargs)
|
||||
super(Spacer, self).__init__(*args, **kwargs)
|
||||
|
||||
self.setFixedHeight(height)
|
||||
|
||||
|
|
@ -53,7 +41,7 @@ class Spacer(QtWidgets.QWidget):
|
|||
|
||||
class OpenPypeMenu(QtWidgets.QWidget):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(self.__class__, self).__init__(*args, **kwargs)
|
||||
super(OpenPypeMenu, self).__init__(*args, **kwargs)
|
||||
|
||||
self.setObjectName("OpenPypeMenu")
|
||||
|
||||
|
|
@ -117,27 +105,27 @@ class OpenPypeMenu(QtWidgets.QWidget):
|
|||
|
||||
def on_workfile_clicked(self):
|
||||
print("Clicked Workfile")
|
||||
launch_workfiles_app()
|
||||
host_tools.show_workfiles()
|
||||
|
||||
def on_create_clicked(self):
|
||||
print("Clicked Create")
|
||||
creator.show()
|
||||
host_tools.show_creator()
|
||||
|
||||
def on_publish_clicked(self):
|
||||
print("Clicked Publish")
|
||||
publish(None)
|
||||
host_tools.show_publish()
|
||||
|
||||
def on_load_clicked(self):
|
||||
print("Clicked Load")
|
||||
loader.show(use_context=True)
|
||||
host_tools.show_loader(use_context=True)
|
||||
|
||||
def on_inventory_clicked(self):
|
||||
print("Clicked Inventory")
|
||||
sceneinventory.show()
|
||||
host_tools.show_scene_inventory()
|
||||
|
||||
def on_libload_clicked(self):
|
||||
print("Clicked Library")
|
||||
libraryloader.show()
|
||||
host_tools.show_library_loader()
|
||||
|
||||
def on_rendernode_clicked(self):
|
||||
from avalon import style
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ Basic avalon integration
|
|||
"""
|
||||
import os
|
||||
|
||||
from openpype.tools import workfiles
|
||||
from avalon import api as avalon
|
||||
from pyblish import api as pyblish
|
||||
from openpype.api import Logger
|
||||
|
|
@ -98,14 +97,3 @@ def on_pyblish_instance_toggled(instance, new_value, old_value):
|
|||
current = attrs["TOOLB_PassThrough"]
|
||||
if current != passthrough:
|
||||
tool.SetAttrs({"TOOLB_PassThrough": passthrough})
|
||||
|
||||
|
||||
def launch_workfiles_app(*args):
|
||||
workdir = os.environ["AVALON_WORKDIR"]
|
||||
workfiles.show(workdir)
|
||||
|
||||
|
||||
def publish(parent):
|
||||
"""Shorthand to publish from within host"""
|
||||
from avalon.tools import publish
|
||||
return publish.show(parent)
|
||||
|
|
|
|||
|
|
@ -3,17 +3,14 @@
|
|||
import os
|
||||
from pathlib import Path
|
||||
import logging
|
||||
import re
|
||||
|
||||
from openpype import lib
|
||||
from openpype.api import (get_current_project_settings)
|
||||
import openpype.hosts.harmony
|
||||
|
||||
import pyblish.api
|
||||
|
||||
from avalon import io, harmony
|
||||
import avalon.api
|
||||
import avalon.tools.sceneinventory
|
||||
|
||||
|
||||
log = logging.getLogger("openpype.hosts.harmony")
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import os
|
|||
import sys
|
||||
import hiero.core
|
||||
from openpype.api import Logger
|
||||
from openpype.tools.utils import host_tools
|
||||
from avalon.api import Session
|
||||
from hiero.ui import findMenuAction
|
||||
|
||||
|
|
@ -41,8 +42,6 @@ def menu_install():
|
|||
apply_colorspace_project, apply_colorspace_clips
|
||||
)
|
||||
# here is the best place to add menu
|
||||
from avalon.tools import creator, sceneinventory
|
||||
from openpype.tools import loader
|
||||
from avalon.vendor.Qt import QtGui
|
||||
|
||||
menu_name = os.environ['AVALON_LABEL']
|
||||
|
|
@ -87,15 +86,15 @@ def menu_install():
|
|||
|
||||
creator_action = menu.addAction("Create ...")
|
||||
creator_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png"))
|
||||
creator_action.triggered.connect(creator.show)
|
||||
creator_action.triggered.connect(host_tools.show_creator)
|
||||
|
||||
loader_action = menu.addAction("Load ...")
|
||||
loader_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png"))
|
||||
loader_action.triggered.connect(loader.show)
|
||||
loader_action.triggered.connect(host_tools.show_loader)
|
||||
|
||||
sceneinventory_action = menu.addAction("Manage ...")
|
||||
sceneinventory_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png"))
|
||||
sceneinventory_action.triggered.connect(sceneinventory.show)
|
||||
sceneinventory_action.triggered.connect(host_tools.show_scene_inventory)
|
||||
menu.addSeparator()
|
||||
|
||||
if os.getenv("OPENPYPE_DEVELOP"):
|
||||
|
|
|
|||
|
|
@ -4,13 +4,12 @@ Basic avalon integration
|
|||
import os
|
||||
import contextlib
|
||||
from collections import OrderedDict
|
||||
from avalon.tools import publish as _publish
|
||||
from openpype.tools import workfiles
|
||||
from avalon.pipeline import AVALON_CONTAINER_ID
|
||||
from avalon import api as avalon
|
||||
from avalon import schema
|
||||
from pyblish import api as pyblish
|
||||
from openpype.api import Logger
|
||||
from openpype.tools.utils import host_tools
|
||||
from . import lib, menu, events
|
||||
|
||||
log = Logger().get_logger(__name__)
|
||||
|
|
@ -211,15 +210,13 @@ def update_container(track_item, data=None):
|
|||
def launch_workfiles_app(*args):
|
||||
''' Wrapping function for workfiles launcher '''
|
||||
|
||||
workdir = os.environ["AVALON_WORKDIR"]
|
||||
|
||||
# show workfile gui
|
||||
workfiles.show(workdir)
|
||||
host_tools.show_workfiles()
|
||||
|
||||
|
||||
def publish(parent):
|
||||
"""Shorthand to publish from within host"""
|
||||
return _publish.show(parent)
|
||||
return host_tools.show_publish(parent)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
|
|
|||
|
|
@ -7,24 +7,30 @@
|
|||
<scriptItem id="avalon_create">
|
||||
<label>Create ...</label>
|
||||
<scriptCode><![CDATA[
|
||||
from avalon.tools import creator
|
||||
creator.show()
|
||||
import hou
|
||||
from openpype.tools.utils import host_tools
|
||||
parent = hou.qt.mainWindow()
|
||||
host_tools.show_creator(parent)
|
||||
]]></scriptCode>
|
||||
</scriptItem>
|
||||
|
||||
<scriptItem id="avalon_load">
|
||||
<label>Load ...</label>
|
||||
<scriptCode><![CDATA[
|
||||
from openpype.tools import loader
|
||||
loader.show(use_context=True)
|
||||
import hou
|
||||
from openpype.tools.utils import host_tools
|
||||
parent = hou.qt.mainWindow()
|
||||
host_tools.show_loader(parent=parent, use_context=True)
|
||||
]]></scriptCode>
|
||||
</scriptItem>
|
||||
|
||||
<scriptItem id="avalon_manage">
|
||||
<label>Manage ...</label>
|
||||
<scriptCode><![CDATA[
|
||||
from avalon.tools import cbsceneinventory
|
||||
cbsceneinventory.show()
|
||||
import hou
|
||||
from openpype.tools.utils import host_tools
|
||||
parent = hou.qt.mainWindow()
|
||||
host_tools.show_scene_inventory(parent)
|
||||
]]></scriptCode>
|
||||
</scriptItem>
|
||||
|
||||
|
|
@ -32,9 +38,9 @@ cbsceneinventory.show()
|
|||
<label>Publish ...</label>
|
||||
<scriptCode><![CDATA[
|
||||
import hou
|
||||
from avalon.tools import publish
|
||||
from openpype.tools.utils import host_tools
|
||||
parent = hou.qt.mainWindow()
|
||||
publish.show(parent)
|
||||
host_tools.show_publish(parent)
|
||||
]]></scriptCode>
|
||||
</scriptItem>
|
||||
|
||||
|
|
@ -43,9 +49,10 @@ publish.show(parent)
|
|||
<scriptItem id="workfiles">
|
||||
<label>Work Files ...</label>
|
||||
<scriptCode><![CDATA[
|
||||
import hou, os
|
||||
from openpype.tools import workfiles
|
||||
workfiles.show(os.environ["AVALON_WORKDIR"])
|
||||
import hou
|
||||
from openpype.tools.utils import host_tools
|
||||
parent = hou.qt.mainWindow()
|
||||
host_tools.show_workfiles(parent)
|
||||
]]></scriptCode>
|
||||
</scriptItem>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from avalon import api as avalon
|
|||
from avalon import pipeline
|
||||
from avalon.maya import suspended_refresh
|
||||
from avalon.maya.pipeline import IS_HEADLESS
|
||||
from openpype.tools import workfiles
|
||||
from openpype.tools.utils import host_tools
|
||||
from pyblish import api as pyblish
|
||||
from openpype.lib import any_outdated
|
||||
import openpype.hosts.maya
|
||||
|
|
@ -208,16 +208,12 @@ def on_init(_):
|
|||
launch_workfiles = os.environ.get("WORKFILES_STARTUP")
|
||||
|
||||
if launch_workfiles:
|
||||
safe_deferred(launch_workfiles_app)
|
||||
safe_deferred(host_tools.show_workfiles)
|
||||
|
||||
if not IS_HEADLESS:
|
||||
safe_deferred(override_toolbox_ui)
|
||||
|
||||
|
||||
def launch_workfiles_app():
|
||||
workfiles.show(os.environ["AVALON_WORKDIR"])
|
||||
|
||||
|
||||
def on_before_save(return_code, _):
|
||||
"""Run validation for scene's FPS prior to saving"""
|
||||
return lib.validate_fps()
|
||||
|
|
@ -313,9 +309,15 @@ def on_task_changed(*args):
|
|||
lib.set_context_settings()
|
||||
lib.update_content_on_context_change()
|
||||
|
||||
msg = " project: {}\n asset: {}\n task:{}".format(
|
||||
avalon.Session["AVALON_PROJECT"],
|
||||
avalon.Session["AVALON_ASSET"],
|
||||
avalon.Session["AVALON_TASK"]
|
||||
)
|
||||
|
||||
lib.show_message(
|
||||
"Context was changed",
|
||||
("Context was changed to {}".format(avalon.Session["AVALON_ASSET"])),
|
||||
("Context was changed to:\n{}".format(msg)),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
"""A set of commands that install overrides to Maya's UI"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from functools import partial
|
||||
|
||||
import maya.cmds as mc
|
||||
import maya.mel as mel
|
||||
from functools import partial
|
||||
import os
|
||||
import logging
|
||||
|
||||
from avalon.maya import pipeline
|
||||
from openpype.api import resources
|
||||
from openpype.tools.utils import host_tools
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
@ -69,39 +75,8 @@ def override_component_mask_commands():
|
|||
|
||||
def override_toolbox_ui():
|
||||
"""Add custom buttons in Toolbox as replacement for Maya web help icon."""
|
||||
inventory = None
|
||||
loader = None
|
||||
launch_workfiles_app = None
|
||||
mayalookassigner = None
|
||||
try:
|
||||
import avalon.tools.sceneinventory as inventory
|
||||
except Exception:
|
||||
log.warning("Could not import SceneInventory tool")
|
||||
|
||||
try:
|
||||
import openpype.tools.loader as loader
|
||||
except Exception:
|
||||
log.warning("Could not import Loader tool")
|
||||
|
||||
try:
|
||||
from avalon.maya.pipeline import launch_workfiles_app
|
||||
except Exception:
|
||||
log.warning("Could not import Workfiles tool")
|
||||
|
||||
try:
|
||||
from openpype.tools import mayalookassigner
|
||||
except Exception:
|
||||
log.warning("Could not import Maya Look assigner tool")
|
||||
|
||||
from openpype.api import resources
|
||||
|
||||
icons = resources.get_resource("icons")
|
||||
|
||||
if not any((
|
||||
mayalookassigner, launch_workfiles_app, loader, inventory
|
||||
)):
|
||||
return
|
||||
|
||||
# Ensure the maya web icon on toolbox exists
|
||||
web_button = "ToolBox|MainToolboxLayout|mayaWebButton"
|
||||
if not mc.iconTextButton(web_button, query=True, exists=True):
|
||||
|
|
@ -120,14 +95,23 @@ def override_toolbox_ui():
|
|||
# Create our controls
|
||||
background_color = (0.267, 0.267, 0.267)
|
||||
controls = []
|
||||
if mayalookassigner:
|
||||
look_assigner = None
|
||||
try:
|
||||
look_assigner = host_tools.get_tool_by_name(
|
||||
"lookassigner",
|
||||
parent=pipeline._parent
|
||||
)
|
||||
except Exception:
|
||||
log.warning("Couldn't create Look assigner window.", exc_info=True)
|
||||
|
||||
if look_assigner is not None:
|
||||
controls.append(
|
||||
mc.iconTextButton(
|
||||
"pype_toolbox_lookmanager",
|
||||
annotation="Look Manager",
|
||||
label="Look Manager",
|
||||
image=os.path.join(icons, "lookmanager.png"),
|
||||
command=lambda: mayalookassigner.show(),
|
||||
command=host_tools.show_look_assigner,
|
||||
bgc=background_color,
|
||||
width=icon_size,
|
||||
height=icon_size,
|
||||
|
|
@ -135,50 +119,53 @@ def override_toolbox_ui():
|
|||
)
|
||||
)
|
||||
|
||||
if launch_workfiles_app:
|
||||
controls.append(
|
||||
mc.iconTextButton(
|
||||
"pype_toolbox_workfiles",
|
||||
annotation="Work Files",
|
||||
label="Work Files",
|
||||
image=os.path.join(icons, "workfiles.png"),
|
||||
command=lambda: launch_workfiles_app(),
|
||||
bgc=background_color,
|
||||
width=icon_size,
|
||||
height=icon_size,
|
||||
parent=parent
|
||||
)
|
||||
controls.append(
|
||||
mc.iconTextButton(
|
||||
"pype_toolbox_workfiles",
|
||||
annotation="Work Files",
|
||||
label="Work Files",
|
||||
image=os.path.join(icons, "workfiles.png"),
|
||||
command=lambda: host_tools.show_workfiles(
|
||||
parent=pipeline._parent
|
||||
),
|
||||
bgc=background_color,
|
||||
width=icon_size,
|
||||
height=icon_size,
|
||||
parent=parent
|
||||
)
|
||||
)
|
||||
|
||||
if loader:
|
||||
controls.append(
|
||||
mc.iconTextButton(
|
||||
"pype_toolbox_loader",
|
||||
annotation="Loader",
|
||||
label="Loader",
|
||||
image=os.path.join(icons, "loader.png"),
|
||||
command=lambda: loader.show(use_context=True),
|
||||
bgc=background_color,
|
||||
width=icon_size,
|
||||
height=icon_size,
|
||||
parent=parent
|
||||
)
|
||||
controls.append(
|
||||
mc.iconTextButton(
|
||||
"pype_toolbox_loader",
|
||||
annotation="Loader",
|
||||
label="Loader",
|
||||
image=os.path.join(icons, "loader.png"),
|
||||
command=lambda: host_tools.show_loader(
|
||||
parent=pipeline._parent, use_context=True
|
||||
),
|
||||
bgc=background_color,
|
||||
width=icon_size,
|
||||
height=icon_size,
|
||||
parent=parent
|
||||
)
|
||||
)
|
||||
|
||||
if inventory:
|
||||
controls.append(
|
||||
mc.iconTextButton(
|
||||
"pype_toolbox_manager",
|
||||
annotation="Inventory",
|
||||
label="Inventory",
|
||||
image=os.path.join(icons, "inventory.png"),
|
||||
command=lambda: inventory.show(),
|
||||
bgc=background_color,
|
||||
width=icon_size,
|
||||
height=icon_size,
|
||||
parent=parent
|
||||
)
|
||||
controls.append(
|
||||
mc.iconTextButton(
|
||||
"pype_toolbox_manager",
|
||||
annotation="Inventory",
|
||||
label="Inventory",
|
||||
image=os.path.join(icons, "inventory.png"),
|
||||
command=lambda: host_tools.show_scene_inventory(
|
||||
parent=pipeline._parent
|
||||
),
|
||||
bgc=background_color,
|
||||
width=icon_size,
|
||||
height=icon_size,
|
||||
parent=parent
|
||||
)
|
||||
)
|
||||
|
||||
# Add the buttons on the bottom and stack
|
||||
# them above each other with side padding
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@ import sys
|
|||
import os
|
||||
import logging
|
||||
|
||||
from avalon.vendor.Qt import QtWidgets, QtGui
|
||||
from avalon.maya import pipeline
|
||||
from openpype.api import BuildWorkfile
|
||||
import maya.cmds as cmds
|
||||
from openpype.settings import get_project_settings
|
||||
from Qt import QtWidgets, QtGui
|
||||
|
||||
self = sys.modules[__name__]
|
||||
import maya.cmds as cmds
|
||||
|
||||
from avalon.maya import pipeline
|
||||
|
||||
from openpype.api import BuildWorkfile
|
||||
from openpype.settings import get_project_settings
|
||||
from openpype.tools.utils import host_tools
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
@ -36,25 +38,15 @@ def deferred():
|
|||
)
|
||||
|
||||
def add_look_assigner_item():
|
||||
import mayalookassigner
|
||||
cmds.menuItem(
|
||||
"Look assigner",
|
||||
parent=pipeline._menu,
|
||||
command=lambda *args: mayalookassigner.show()
|
||||
command=lambda *args: host_tools.show_look_assigner(
|
||||
pipeline._parent
|
||||
)
|
||||
)
|
||||
|
||||
def modify_workfiles():
|
||||
from openpype.tools import workfiles
|
||||
|
||||
def launch_workfiles_app(*_args, **_kwargs):
|
||||
workfiles.show(
|
||||
os.path.join(
|
||||
cmds.workspace(query=True, rootDirectory=True),
|
||||
cmds.workspace(fileRuleEntry="scene")
|
||||
),
|
||||
parent=pipeline._parent
|
||||
)
|
||||
|
||||
# Find the pipeline menu
|
||||
top_menu = _get_menu()
|
||||
|
||||
|
|
@ -75,7 +67,7 @@ def deferred():
|
|||
cmds.menuItem(
|
||||
"Work Files",
|
||||
parent=pipeline._menu,
|
||||
command=launch_workfiles_app,
|
||||
command=lambda *args: host_tools.show_workfiles(pipeline._parent),
|
||||
insertAfter=after_action
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ class ReferenceLoader(api.Loader):
|
|||
count = options.get("count") or 1
|
||||
for c in range(0, count):
|
||||
namespace = namespace or lib.unique_namespace(
|
||||
asset["name"] + "_",
|
||||
"{}_{}_".format(asset["name"], context["subset"]["name"]),
|
||||
prefix="_" if asset["name"][0].isdigit() else "",
|
||||
suffix="_",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -41,14 +41,13 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
|
|||
family = "model"
|
||||
|
||||
with maya.maintained_selection():
|
||||
|
||||
groupName = "{}:{}".format(namespace, name)
|
||||
groupName = "{}:_GRP".format(namespace)
|
||||
cmds.loadPlugin("AbcImport.mll", quiet=True)
|
||||
nodes = cmds.file(self.fname,
|
||||
namespace=namespace,
|
||||
sharedReferenceFile=False,
|
||||
groupReference=True,
|
||||
groupName="{}:{}".format(namespace, name),
|
||||
groupName=groupName,
|
||||
reference=True,
|
||||
returnNewNodes=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -244,17 +244,17 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
|
|||
# metadata file will be located in top-most common
|
||||
# directory.
|
||||
# TODO: use `os.path.commonpath()` after switch to Python 3
|
||||
publish_meta_path = os.path.normpath(publish_meta_path)
|
||||
common_publish_meta_path = os.path.splitdrive(
|
||||
publish_meta_path)[0]
|
||||
if common_publish_meta_path:
|
||||
common_publish_meta_path += os.path.sep
|
||||
for part in publish_meta_path.split("/"):
|
||||
for part in publish_meta_path.replace(
|
||||
common_publish_meta_path, "").split(os.path.sep):
|
||||
common_publish_meta_path = os.path.join(
|
||||
common_publish_meta_path, part)
|
||||
if part == expected_layer_name:
|
||||
break
|
||||
common_publish_meta_path = common_publish_meta_path.replace(
|
||||
"\\", "/")
|
||||
self.log.info(
|
||||
"Publish meta path: {}".format(common_publish_meta_path))
|
||||
|
||||
|
|
|
|||
|
|
@ -205,6 +205,9 @@ class ExtractLook(openpype.api.Extractor):
|
|||
lookdata = instance.data["lookData"]
|
||||
relationships = lookdata["relationships"]
|
||||
sets = relationships.keys()
|
||||
if not sets:
|
||||
self.log.info("No sets found")
|
||||
return
|
||||
|
||||
results = self.process_resources(instance, staging_dir=dir_path)
|
||||
transfers = results["fileTransfers"]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ from collections import OrderedDict
|
|||
|
||||
|
||||
from avalon import api, io, lib
|
||||
from openpype.tools import workfiles
|
||||
import avalon.nuke
|
||||
from avalon.nuke import lib as anlib
|
||||
from avalon.nuke import (
|
||||
|
|
@ -24,7 +23,7 @@ from openpype.api import (
|
|||
get_current_project_settings,
|
||||
ApplicationManager
|
||||
)
|
||||
|
||||
from openpype.tools.utils import host_tools
|
||||
import nuke
|
||||
|
||||
from .utils import set_context_favorites
|
||||
|
|
@ -1032,27 +1031,6 @@ class WorkfileSettings(object):
|
|||
log.error(msg)
|
||||
nuke.message(msg)
|
||||
|
||||
bbox = self._asset_entity.get('data', {}).get('crop')
|
||||
|
||||
if bbox:
|
||||
try:
|
||||
x, y, r, t = bbox.split(".")
|
||||
data.update(
|
||||
{
|
||||
"x": int(x),
|
||||
"y": int(y),
|
||||
"r": int(r),
|
||||
"t": int(t),
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
bbox = None
|
||||
msg = ("{}:{} \nFormat:Crop need to be set with dots, "
|
||||
"example: 0.0.1920.1080, "
|
||||
"/nSetting to default").format(__name__, e)
|
||||
log.error(msg)
|
||||
nuke.message(msg)
|
||||
|
||||
existing_format = None
|
||||
for format in nuke.formats():
|
||||
if data["name"] == format.name():
|
||||
|
|
@ -1064,12 +1042,6 @@ class WorkfileSettings(object):
|
|||
existing_format.setWidth(data["width"])
|
||||
existing_format.setHeight(data["height"])
|
||||
existing_format.setPixelAspect(data["pixel_aspect"])
|
||||
|
||||
if bbox:
|
||||
existing_format.setX(data["x"])
|
||||
existing_format.setY(data["y"])
|
||||
existing_format.setR(data["r"])
|
||||
existing_format.setT(data["t"])
|
||||
else:
|
||||
format_string = self.make_format_string(**data)
|
||||
log.info("Creating new format: {}".format(format_string))
|
||||
|
|
@ -1689,7 +1661,7 @@ def launch_workfiles_app():
|
|||
|
||||
if not opnl.workfiles_launched:
|
||||
opnl.workfiles_launched = True
|
||||
workfiles.show(os.environ["AVALON_WORKDIR"])
|
||||
host_tools.show_workfiles()
|
||||
|
||||
|
||||
def process_workfile_builder():
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from avalon.api import Session
|
|||
|
||||
from .lib import WorkfileSettings
|
||||
from openpype.api import Logger, BuildWorkfile, get_current_project_settings
|
||||
from openpype.tools import workfiles
|
||||
from openpype.tools.utils import host_tools
|
||||
|
||||
log = Logger().get_logger(__name__)
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ def install():
|
|||
menu.removeItem(rm_item[1].name())
|
||||
menu.addCommand(
|
||||
name,
|
||||
workfiles.show,
|
||||
host_tools.show_workfiles,
|
||||
index=2
|
||||
)
|
||||
menu.addSeparator(index=3)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
import random
|
||||
import string
|
||||
|
||||
import avalon.nuke
|
||||
from avalon.nuke import lib as anlib
|
||||
from avalon import api
|
||||
|
||||
from openpype.api import (
|
||||
get_current_project_settings,
|
||||
PypeCreatorMixin
|
||||
|
|
@ -23,3 +29,68 @@ class PypeCreator(PypeCreatorMixin, avalon.nuke.pipeline.Creator):
|
|||
self.log.error(msg + '\n\nPlease use other subset name!')
|
||||
raise NameError("`{0}: {1}".format(__name__, msg))
|
||||
return
|
||||
|
||||
|
||||
def get_review_presets_config():
|
||||
settings = get_current_project_settings()
|
||||
review_profiles = (
|
||||
settings["global"]
|
||||
["publish"]
|
||||
["ExtractReview"]
|
||||
["profiles"]
|
||||
)
|
||||
|
||||
outputs = {}
|
||||
for profile in review_profiles:
|
||||
outputs.update(profile.get("outputs", {}))
|
||||
|
||||
return [str(name) for name, _prop in outputs.items()]
|
||||
|
||||
|
||||
class NukeLoader(api.Loader):
|
||||
container_id_knob = "containerId"
|
||||
container_id = ''.join(random.choice(
|
||||
string.ascii_uppercase + string.digits) for _ in range(10))
|
||||
|
||||
def get_container_id(self, node):
|
||||
id_knob = node.knobs().get(self.container_id_knob)
|
||||
return id_knob.value() if id_knob else None
|
||||
|
||||
def get_members(self, source):
|
||||
"""Return nodes that has same 'containerId' as `source`"""
|
||||
source_id = self.get_container_id(source)
|
||||
return [node for node in nuke.allNodes(recurseGroups=True)
|
||||
if self.get_container_id(node) == source_id
|
||||
and node is not source] if source_id else []
|
||||
|
||||
def set_as_member(self, node):
|
||||
source_id = self.get_container_id(node)
|
||||
|
||||
if source_id:
|
||||
node[self.container_id_knob].setValue(self.container_id)
|
||||
else:
|
||||
HIDEN_FLAG = 0x00040000
|
||||
_knob = anlib.Knobby(
|
||||
"String_Knob",
|
||||
self.container_id,
|
||||
flags=[nuke.READ_ONLY, HIDEN_FLAG])
|
||||
knob = _knob.create(self.container_id_knob)
|
||||
node.addKnob(knob)
|
||||
|
||||
def clear_members(self, parent_node):
|
||||
members = self.get_members(parent_node)
|
||||
|
||||
dependent_nodes = None
|
||||
for node in members:
|
||||
_depndc = [n for n in node.dependent() if n not in members]
|
||||
if not _depndc:
|
||||
continue
|
||||
|
||||
dependent_nodes = _depndc
|
||||
break
|
||||
|
||||
for member in members:
|
||||
self.log.info("removing node: `{}".format(member.name()))
|
||||
nuke.delete(member)
|
||||
|
||||
return dependent_nodes
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ class CreateWriteRender(plugin.PypeCreator):
|
|||
"fpath_template": ("{work}/renders/nuke/{subset}"
|
||||
"/{subset}.{frame}.{ext}")})
|
||||
|
||||
# add crop node to cut off all outside of format bounding box
|
||||
# add reformat node to cut off all outside of format bounding box
|
||||
# get width and height
|
||||
try:
|
||||
width, height = (selected_node.width(), selected_node.height())
|
||||
|
|
@ -109,15 +109,11 @@ class CreateWriteRender(plugin.PypeCreator):
|
|||
|
||||
_prenodes = [
|
||||
{
|
||||
"name": "Crop01",
|
||||
"class": "Crop",
|
||||
"name": "Reformat01",
|
||||
"class": "Reformat",
|
||||
"knobs": [
|
||||
("box", [
|
||||
0.0,
|
||||
0.0,
|
||||
width,
|
||||
height
|
||||
])
|
||||
("resize", 0),
|
||||
("black_outside", 1),
|
||||
],
|
||||
"dependent": None
|
||||
}
|
||||
|
|
|
|||
37
openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py
Normal file
37
openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
from avalon import api, style
|
||||
from avalon.nuke import lib as anlib
|
||||
from openpype.api import (
|
||||
Logger)
|
||||
|
||||
|
||||
class RepairOldLoaders(api.InventoryAction):
|
||||
|
||||
label = "Repair Old Loaders"
|
||||
icon = "gears"
|
||||
color = style.colors.alert
|
||||
|
||||
log = Logger().get_logger(__name__)
|
||||
|
||||
def process(self, containers):
|
||||
import nuke
|
||||
new_loader = "LoadClip"
|
||||
|
||||
for cdata in containers:
|
||||
orig_loader = cdata["loader"]
|
||||
orig_name = cdata["objectName"]
|
||||
if orig_loader not in ["LoadSequence", "LoadMov"]:
|
||||
self.log.warning(
|
||||
"This repair action is only working on "
|
||||
"`LoadSequence` and `LoadMov` Loaders")
|
||||
continue
|
||||
|
||||
new_name = orig_name.replace(orig_loader, new_loader)
|
||||
node = nuke.toNode(cdata["objectName"])
|
||||
|
||||
cdata.update({
|
||||
"loader": new_loader,
|
||||
"objectName": new_name
|
||||
})
|
||||
node["name"].setValue(new_name)
|
||||
# get data from avalon knob
|
||||
anlib.set_avalon_knob_data(node, cdata)
|
||||
|
|
@ -8,10 +8,10 @@ class SelectContainers(api.InventoryAction):
|
|||
color = "#d8d8d8"
|
||||
|
||||
def process(self, containers):
|
||||
|
||||
import nuke
|
||||
import avalon.nuke
|
||||
|
||||
nodes = [i["_node"] for i in containers]
|
||||
nodes = [nuke.toNode(i["objectName"]) for i in containers]
|
||||
|
||||
with avalon.nuke.viewer_update_and_undo_stop():
|
||||
# clear previous_selection
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
# from avalon import api, style
|
||||
# from avalon.vendor.Qt import QtGui, QtWidgets
|
||||
#
|
||||
# import avalon.fusion
|
||||
#
|
||||
#
|
||||
# class FusionSetToolColor(api.InventoryAction):
|
||||
# """Update the color of the selected tools"""
|
||||
#
|
||||
# label = "Set Tool Color"
|
||||
# icon = "plus"
|
||||
# color = "#d8d8d8"
|
||||
# _fallback_color = QtGui.QColor(1.0, 1.0, 1.0)
|
||||
#
|
||||
# def process(self, containers):
|
||||
# """Color all selected tools the selected colors"""
|
||||
#
|
||||
# result = []
|
||||
# comp = avalon.fusion.get_current_comp()
|
||||
#
|
||||
# # Get tool color
|
||||
# first = containers[0]
|
||||
# tool = first["_node"]
|
||||
# color = tool.TileColor
|
||||
#
|
||||
# if color is not None:
|
||||
# qcolor = QtGui.QColor().fromRgbF(color["R"], color["G"], color["B"])
|
||||
# else:
|
||||
# qcolor = self._fallback_color
|
||||
#
|
||||
# # Launch pick color
|
||||
# picked_color = self.get_color_picker(qcolor)
|
||||
# if not picked_color:
|
||||
# return
|
||||
#
|
||||
# with avalon.fusion.comp_lock_and_undo_chunk(comp):
|
||||
# for container in containers:
|
||||
# # Convert color to RGB 0-1 floats
|
||||
# rgb_f = picked_color.getRgbF()
|
||||
# rgb_f_table = {"R": rgb_f[0], "G": rgb_f[1], "B": rgb_f[2]}
|
||||
#
|
||||
# # Update tool
|
||||
# tool = container["_node"]
|
||||
# tool.TileColor = rgb_f_table
|
||||
#
|
||||
# result.append(container)
|
||||
#
|
||||
# return result
|
||||
#
|
||||
# def get_color_picker(self, color):
|
||||
# """Launch color picker and return chosen color
|
||||
#
|
||||
# Args:
|
||||
# color(QtGui.QColor): Start color to display
|
||||
#
|
||||
# Returns:
|
||||
# QtGui.QColor
|
||||
#
|
||||
# """
|
||||
#
|
||||
# color_dialog = QtWidgets.QColorDialog(color)
|
||||
# color_dialog.setStyleSheet(style.load_stylesheet())
|
||||
#
|
||||
# accepted = color_dialog.exec_()
|
||||
# if not accepted:
|
||||
# return
|
||||
#
|
||||
# return color_dialog.selectedColor()
|
||||
371
openpype/hosts/nuke/plugins/load/load_clip.py
Normal file
371
openpype/hosts/nuke/plugins/load/load_clip.py
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
import nuke
|
||||
from avalon.vendor import qargparse
|
||||
from avalon import api, io
|
||||
|
||||
from openpype.hosts.nuke.api.lib import (
|
||||
get_imageio_input_colorspace
|
||||
)
|
||||
from avalon.nuke import (
|
||||
containerise,
|
||||
update_container,
|
||||
viewer_update_and_undo_stop,
|
||||
maintained_selection
|
||||
)
|
||||
from openpype.hosts.nuke.api import plugin
|
||||
|
||||
|
||||
class LoadClip(plugin.NukeLoader):
|
||||
"""Load clip into Nuke
|
||||
|
||||
Either it is image sequence or video file.
|
||||
"""
|
||||
|
||||
families = [
|
||||
"source",
|
||||
"plate",
|
||||
"render",
|
||||
"prerender",
|
||||
"review"
|
||||
]
|
||||
representations = [
|
||||
"exr",
|
||||
"dpx",
|
||||
"mov",
|
||||
"review",
|
||||
"mp4"
|
||||
]
|
||||
|
||||
label = "Load Clip"
|
||||
order = -20
|
||||
icon = "file-video-o"
|
||||
color = "white"
|
||||
|
||||
script_start = int(nuke.root()["first_frame"].value())
|
||||
|
||||
# option gui
|
||||
defaults = {
|
||||
"start_at_workfile": True
|
||||
}
|
||||
|
||||
options = [
|
||||
qargparse.Boolean(
|
||||
"start_at_workfile",
|
||||
help="Load at workfile start frame",
|
||||
default=True
|
||||
)
|
||||
]
|
||||
|
||||
node_name_template = "{class_name}_{ext}"
|
||||
|
||||
@classmethod
|
||||
def get_representations(cls):
|
||||
return (
|
||||
cls.representations
|
||||
+ cls._representations
|
||||
+ plugin.get_review_presets_config()
|
||||
)
|
||||
|
||||
def load(self, context, name, namespace, options):
|
||||
|
||||
is_sequence = len(context["representation"]["files"]) > 1
|
||||
|
||||
file = self.fname.replace("\\", "/")
|
||||
|
||||
start_at_workfile = options.get(
|
||||
"start_at_workfile", self.defaults["start_at_workfile"])
|
||||
|
||||
version = context['version']
|
||||
version_data = version.get("data", {})
|
||||
repr_id = context["representation"]["_id"]
|
||||
colorspace = version_data.get("colorspace")
|
||||
iio_colorspace = get_imageio_input_colorspace(file)
|
||||
repr_cont = context["representation"]["context"]
|
||||
|
||||
self.log.info("version_data: {}\n".format(version_data))
|
||||
self.log.debug(
|
||||
"Representation id `{}` ".format(repr_id))
|
||||
|
||||
self.handle_start = version_data.get("handleStart", 0)
|
||||
self.handle_end = version_data.get("handleEnd", 0)
|
||||
|
||||
first = version_data.get("frameStart", None)
|
||||
last = version_data.get("frameEnd", None)
|
||||
first -= self.handle_start
|
||||
last += self.handle_end
|
||||
|
||||
if not is_sequence:
|
||||
duration = last - first + 1
|
||||
first = 1
|
||||
last = first + duration
|
||||
elif "#" not in file:
|
||||
frame = repr_cont.get("frame")
|
||||
assert frame, "Representation is not sequence"
|
||||
|
||||
padding = len(frame)
|
||||
file = file.replace(frame, "#" * padding)
|
||||
|
||||
# Fallback to asset name when namespace is None
|
||||
if namespace is None:
|
||||
namespace = context['asset']['name']
|
||||
|
||||
if not file:
|
||||
self.log.warning(
|
||||
"Representation id `{}` is failing to load".format(repr_id))
|
||||
return
|
||||
|
||||
name_data = {
|
||||
"asset": repr_cont["asset"],
|
||||
"subset": repr_cont["subset"],
|
||||
"representation": context["representation"]["name"],
|
||||
"ext": repr_cont["representation"],
|
||||
"id": context["representation"]["_id"],
|
||||
"class_name": self.__class__.__name__
|
||||
}
|
||||
|
||||
read_name = self.node_name_template.format(**name_data)
|
||||
|
||||
# Create the Loader with the filename path set
|
||||
read_node = nuke.createNode(
|
||||
"Read",
|
||||
"name {}".format(read_name))
|
||||
|
||||
# to avoid multiple undo steps for rest of process
|
||||
# we will switch off undo-ing
|
||||
with viewer_update_and_undo_stop():
|
||||
read_node["file"].setValue(file)
|
||||
|
||||
# Set colorspace defined in version data
|
||||
if colorspace:
|
||||
read_node["colorspace"].setValue(str(colorspace))
|
||||
elif iio_colorspace is not None:
|
||||
read_node["colorspace"].setValue(iio_colorspace)
|
||||
|
||||
self.set_range_to_node(read_node, first, last, start_at_workfile)
|
||||
|
||||
# add additional metadata from the version to imprint Avalon knob
|
||||
add_keys = ["frameStart", "frameEnd",
|
||||
"source", "colorspace", "author", "fps", "version",
|
||||
"handleStart", "handleEnd"]
|
||||
|
||||
data_imprint = {}
|
||||
for k in add_keys:
|
||||
if k == 'version':
|
||||
data_imprint.update({k: context["version"]['name']})
|
||||
else:
|
||||
data_imprint.update(
|
||||
{k: context["version"]['data'].get(k, str(None))})
|
||||
|
||||
data_imprint.update({"objectName": read_name})
|
||||
|
||||
read_node["tile_color"].setValue(int("0x4ecd25ff", 16))
|
||||
|
||||
container = containerise(
|
||||
read_node,
|
||||
name=name,
|
||||
namespace=namespace,
|
||||
context=context,
|
||||
loader=self.__class__.__name__,
|
||||
data=data_imprint)
|
||||
|
||||
if version_data.get("retime", None):
|
||||
self.make_retimes(read_node, version_data)
|
||||
|
||||
self.set_as_member(read_node)
|
||||
|
||||
return container
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
||||
def update(self, container, representation):
|
||||
"""Update the Loader's path
|
||||
|
||||
Nuke automatically tries to reset some variables when changing
|
||||
the loader's path to a new file. These automatic changes are to its
|
||||
inputs:
|
||||
|
||||
"""
|
||||
|
||||
is_sequence = len(representation["files"]) > 1
|
||||
|
||||
read_node = nuke.toNode(container['objectName'])
|
||||
file = api.get_representation_path(representation).replace("\\", "/")
|
||||
|
||||
start_at_workfile = bool("start at" in read_node['frame_mode'].value())
|
||||
|
||||
version = io.find_one({
|
||||
"type": "version",
|
||||
"_id": representation["parent"]
|
||||
})
|
||||
version_data = version.get("data", {})
|
||||
repr_id = representation["_id"]
|
||||
colorspace = version_data.get("colorspace")
|
||||
iio_colorspace = get_imageio_input_colorspace(file)
|
||||
repr_cont = representation["context"]
|
||||
|
||||
self.handle_start = version_data.get("handleStart", 0)
|
||||
self.handle_end = version_data.get("handleEnd", 0)
|
||||
|
||||
first = version_data.get("frameStart", None)
|
||||
last = version_data.get("frameEnd", None)
|
||||
first -= self.handle_start
|
||||
last += self.handle_end
|
||||
|
||||
if not is_sequence:
|
||||
duration = last - first + 1
|
||||
first = 1
|
||||
last = first + duration
|
||||
elif "#" not in file:
|
||||
frame = repr_cont.get("frame")
|
||||
assert frame, "Representation is not sequence"
|
||||
|
||||
padding = len(frame)
|
||||
file = file.replace(frame, "#" * padding)
|
||||
|
||||
if not file:
|
||||
self.log.warning(
|
||||
"Representation id `{}` is failing to load".format(repr_id))
|
||||
return
|
||||
|
||||
read_node["file"].setValue(file)
|
||||
|
||||
# to avoid multiple undo steps for rest of process
|
||||
# we will switch off undo-ing
|
||||
with viewer_update_and_undo_stop():
|
||||
|
||||
# Set colorspace defined in version data
|
||||
if colorspace:
|
||||
read_node["colorspace"].setValue(str(colorspace))
|
||||
elif iio_colorspace is not None:
|
||||
read_node["colorspace"].setValue(iio_colorspace)
|
||||
|
||||
self.set_range_to_node(read_node, first, last, start_at_workfile)
|
||||
|
||||
updated_dict = {
|
||||
"representation": str(representation["_id"]),
|
||||
"frameStart": str(first),
|
||||
"frameEnd": str(last),
|
||||
"version": str(version.get("name")),
|
||||
"colorspace": colorspace,
|
||||
"source": version_data.get("source"),
|
||||
"handleStart": str(self.handle_start),
|
||||
"handleEnd": str(self.handle_end),
|
||||
"fps": str(version_data.get("fps")),
|
||||
"author": version_data.get("author"),
|
||||
"outputDir": version_data.get("outputDir"),
|
||||
}
|
||||
|
||||
# change color of read_node
|
||||
# get all versions in list
|
||||
versions = io.find({
|
||||
"type": "version",
|
||||
"parent": version["parent"]
|
||||
}).distinct('name')
|
||||
|
||||
max_version = max(versions)
|
||||
|
||||
if version.get("name") not in [max_version]:
|
||||
read_node["tile_color"].setValue(int("0xd84f20ff", 16))
|
||||
else:
|
||||
read_node["tile_color"].setValue(int("0x4ecd25ff", 16))
|
||||
|
||||
# Update the imprinted representation
|
||||
update_container(
|
||||
read_node,
|
||||
updated_dict
|
||||
)
|
||||
self.log.info("udated to version: {}".format(version.get("name")))
|
||||
|
||||
if version_data.get("retime", None):
|
||||
self.make_retimes(read_node, version_data)
|
||||
else:
|
||||
self.clear_members(read_node)
|
||||
|
||||
self.set_as_member(read_node)
|
||||
|
||||
def set_range_to_node(self, read_node, first, last, start_at_workfile):
|
||||
read_node['origfirst'].setValue(int(first))
|
||||
read_node['first'].setValue(int(first))
|
||||
read_node['origlast'].setValue(int(last))
|
||||
read_node['last'].setValue(int(last))
|
||||
|
||||
# set start frame depending on workfile or version
|
||||
self.loader_shift(read_node, start_at_workfile)
|
||||
|
||||
def remove(self, container):
|
||||
|
||||
from avalon.nuke import viewer_update_and_undo_stop
|
||||
|
||||
read_node = nuke.toNode(container['objectName'])
|
||||
assert read_node.Class() == "Read", "Must be Read"
|
||||
|
||||
with viewer_update_and_undo_stop():
|
||||
members = self.get_members(read_node)
|
||||
nuke.delete(read_node)
|
||||
for member in members:
|
||||
nuke.delete(member)
|
||||
|
||||
def make_retimes(self, parent_node, version_data):
|
||||
''' Create all retime and timewarping nodes with coppied animation '''
|
||||
speed = version_data.get('speed', 1)
|
||||
time_warp_nodes = version_data.get('timewarps', [])
|
||||
last_node = None
|
||||
source_id = self.get_container_id(parent_node)
|
||||
self.log.info("__ source_id: {}".format(source_id))
|
||||
self.log.info("__ members: {}".format(self.get_members(parent_node)))
|
||||
dependent_nodes = self.clear_members(parent_node)
|
||||
|
||||
with maintained_selection():
|
||||
parent_node['selected'].setValue(True)
|
||||
|
||||
if speed != 1:
|
||||
rtn = nuke.createNode(
|
||||
"Retime",
|
||||
"speed {}".format(speed))
|
||||
|
||||
rtn["before"].setValue("continue")
|
||||
rtn["after"].setValue("continue")
|
||||
rtn["input.first_lock"].setValue(True)
|
||||
rtn["input.first"].setValue(
|
||||
self.script_start
|
||||
)
|
||||
self.set_as_member(rtn)
|
||||
last_node = rtn
|
||||
|
||||
if time_warp_nodes != []:
|
||||
start_anim = self.script_start + (self.handle_start / speed)
|
||||
for timewarp in time_warp_nodes:
|
||||
twn = nuke.createNode(
|
||||
timewarp["Class"],
|
||||
"name {}".format(timewarp["name"])
|
||||
)
|
||||
if isinstance(timewarp["lookup"], list):
|
||||
# if array for animation
|
||||
twn["lookup"].setAnimated()
|
||||
for i, value in enumerate(timewarp["lookup"]):
|
||||
twn["lookup"].setValueAt(
|
||||
(start_anim + i) + value,
|
||||
(start_anim + i))
|
||||
else:
|
||||
# if static value `int`
|
||||
twn["lookup"].setValue(timewarp["lookup"])
|
||||
|
||||
self.set_as_member(twn)
|
||||
last_node = twn
|
||||
|
||||
if dependent_nodes:
|
||||
# connect to original inputs
|
||||
for i, n in enumerate(dependent_nodes):
|
||||
last_node.setInput(i, n)
|
||||
|
||||
def loader_shift(self, read_node, workfile_start=False):
|
||||
""" Set start frame of read node to a workfile start
|
||||
|
||||
Args:
|
||||
read_node (nuke.Node): The nuke's read node
|
||||
workfile_start (bool): set workfile start frame if true
|
||||
|
||||
"""
|
||||
if workfile_start:
|
||||
read_node['frame_mode'].setValue("start at")
|
||||
read_node['frame'].setValue(str(self.script_start))
|
||||
|
|
@ -12,7 +12,15 @@ from openpype.hosts.nuke.api.lib import (
|
|||
class LoadImage(api.Loader):
|
||||
"""Load still image into Nuke"""
|
||||
|
||||
families = ["render", "source", "plate", "review", "image"]
|
||||
families = [
|
||||
"render2d",
|
||||
"source",
|
||||
"plate",
|
||||
"render",
|
||||
"prerender",
|
||||
"review",
|
||||
"image"
|
||||
]
|
||||
representations = ["exr", "dpx", "jpg", "jpeg", "png", "psd", "tiff"]
|
||||
|
||||
label = "Load Image"
|
||||
|
|
@ -33,6 +41,10 @@ class LoadImage(api.Loader):
|
|||
)
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_representations(cls):
|
||||
return cls.representations + cls._representations
|
||||
|
||||
def load(self, context, name, namespace, options):
|
||||
from avalon.nuke import (
|
||||
containerise,
|
||||
|
|
|
|||
|
|
@ -1,347 +0,0 @@
|
|||
import nuke
|
||||
from avalon.vendor import qargparse
|
||||
from avalon import api, io
|
||||
from openpype.api import get_current_project_settings
|
||||
from openpype.hosts.nuke.api.lib import (
|
||||
get_imageio_input_colorspace
|
||||
)
|
||||
|
||||
|
||||
def add_review_presets_config():
|
||||
returning = {
|
||||
"families": list(),
|
||||
"representations": list()
|
||||
}
|
||||
settings = get_current_project_settings()
|
||||
review_profiles = (
|
||||
settings["global"]
|
||||
["publish"]
|
||||
["ExtractReview"]
|
||||
["profiles"]
|
||||
)
|
||||
|
||||
outputs = {}
|
||||
for profile in review_profiles:
|
||||
outputs.update(profile.get("outputs", {}))
|
||||
|
||||
for output, properities in outputs.items():
|
||||
returning["representations"].append(output)
|
||||
returning["families"] += properities.get("families", [])
|
||||
|
||||
return returning
|
||||
|
||||
|
||||
class LoadMov(api.Loader):
|
||||
"""Load mov file into Nuke"""
|
||||
families = ["render", "source", "plate", "review"]
|
||||
representations = ["mov", "review", "mp4"]
|
||||
|
||||
label = "Load mov"
|
||||
order = -10
|
||||
icon = "code-fork"
|
||||
color = "orange"
|
||||
|
||||
first_frame = nuke.root()["first_frame"].value()
|
||||
|
||||
# options gui
|
||||
defaults = {
|
||||
"start_at_workfile": True
|
||||
}
|
||||
|
||||
options = [
|
||||
qargparse.Boolean(
|
||||
"start_at_workfile",
|
||||
help="Load at workfile start frame",
|
||||
default=True
|
||||
)
|
||||
]
|
||||
|
||||
node_name_template = "{class_name}_{ext}"
|
||||
|
||||
def load(self, context, name, namespace, options):
|
||||
from avalon.nuke import (
|
||||
containerise,
|
||||
viewer_update_and_undo_stop
|
||||
)
|
||||
|
||||
start_at_workfile = options.get(
|
||||
"start_at_workfile", self.defaults["start_at_workfile"])
|
||||
|
||||
version = context['version']
|
||||
version_data = version.get("data", {})
|
||||
repr_id = context["representation"]["_id"]
|
||||
|
||||
self.handle_start = version_data.get("handleStart", 0)
|
||||
self.handle_end = version_data.get("handleEnd", 0)
|
||||
|
||||
orig_first = version_data.get("frameStart")
|
||||
orig_last = version_data.get("frameEnd")
|
||||
diff = orig_first - 1
|
||||
|
||||
first = orig_first - diff
|
||||
last = orig_last - diff
|
||||
|
||||
colorspace = version_data.get("colorspace")
|
||||
repr_cont = context["representation"]["context"]
|
||||
|
||||
self.log.debug(
|
||||
"Representation id `{}` ".format(repr_id))
|
||||
|
||||
context["representation"]["_id"]
|
||||
# create handles offset (only to last, because of mov)
|
||||
last += self.handle_start + self.handle_end
|
||||
|
||||
# Fallback to asset name when namespace is None
|
||||
if namespace is None:
|
||||
namespace = context['asset']['name']
|
||||
|
||||
file = self.fname
|
||||
|
||||
if not file:
|
||||
self.log.warning(
|
||||
"Representation id `{}` is failing to load".format(repr_id))
|
||||
return
|
||||
|
||||
file = file.replace("\\", "/")
|
||||
|
||||
name_data = {
|
||||
"asset": repr_cont["asset"],
|
||||
"subset": repr_cont["subset"],
|
||||
"representation": context["representation"]["name"],
|
||||
"ext": repr_cont["representation"],
|
||||
"id": context["representation"]["_id"],
|
||||
"class_name": self.__class__.__name__
|
||||
}
|
||||
|
||||
read_name = self.node_name_template.format(**name_data)
|
||||
|
||||
read_node = nuke.createNode(
|
||||
"Read",
|
||||
"name {}".format(read_name)
|
||||
)
|
||||
|
||||
# to avoid multiple undo steps for rest of process
|
||||
# we will switch off undo-ing
|
||||
with viewer_update_and_undo_stop():
|
||||
read_node["file"].setValue(file)
|
||||
|
||||
read_node["origfirst"].setValue(first)
|
||||
read_node["first"].setValue(first)
|
||||
read_node["origlast"].setValue(last)
|
||||
read_node["last"].setValue(last)
|
||||
read_node['frame_mode'].setValue("start at")
|
||||
|
||||
if start_at_workfile:
|
||||
# start at workfile start
|
||||
read_node['frame'].setValue(str(self.first_frame))
|
||||
else:
|
||||
# start at version frame start
|
||||
read_node['frame'].setValue(
|
||||
str(orig_first - self.handle_start))
|
||||
|
||||
if colorspace:
|
||||
read_node["colorspace"].setValue(str(colorspace))
|
||||
|
||||
preset_clrsp = get_imageio_input_colorspace(file)
|
||||
|
||||
if preset_clrsp is not None:
|
||||
read_node["colorspace"].setValue(preset_clrsp)
|
||||
|
||||
# add additional metadata from the version to imprint Avalon knob
|
||||
add_keys = [
|
||||
"frameStart", "frameEnd", "handles", "source", "author",
|
||||
"fps", "version", "handleStart", "handleEnd"
|
||||
]
|
||||
|
||||
data_imprint = {}
|
||||
for key in add_keys:
|
||||
if key == 'version':
|
||||
data_imprint.update({
|
||||
key: context["version"]['name']
|
||||
})
|
||||
else:
|
||||
data_imprint.update({
|
||||
key: context["version"]['data'].get(key, str(None))
|
||||
})
|
||||
|
||||
data_imprint.update({"objectName": read_name})
|
||||
|
||||
read_node["tile_color"].setValue(int("0x4ecd25ff", 16))
|
||||
|
||||
if version_data.get("retime", None):
|
||||
speed = version_data.get("speed", 1)
|
||||
time_warp_nodes = version_data.get("timewarps", [])
|
||||
self.make_retimes(speed, time_warp_nodes)
|
||||
|
||||
return containerise(
|
||||
read_node,
|
||||
name=name,
|
||||
namespace=namespace,
|
||||
context=context,
|
||||
loader=self.__class__.__name__,
|
||||
data=data_imprint
|
||||
)
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
||||
def update(self, container, representation):
|
||||
"""Update the Loader's path
|
||||
|
||||
Nuke automatically tries to reset some variables when changing
|
||||
the loader's path to a new file. These automatic changes are to its
|
||||
inputs:
|
||||
|
||||
"""
|
||||
|
||||
from avalon.nuke import (
|
||||
update_container
|
||||
)
|
||||
|
||||
read_node = nuke.toNode(container['objectName'])
|
||||
|
||||
assert read_node.Class() == "Read", "Must be Read"
|
||||
|
||||
file = self.fname
|
||||
|
||||
if not file:
|
||||
repr_id = representation["_id"]
|
||||
self.log.warning(
|
||||
"Representation id `{}` is failing to load".format(repr_id))
|
||||
return
|
||||
|
||||
file = file.replace("\\", "/")
|
||||
|
||||
# Get start frame from version data
|
||||
version = io.find_one({
|
||||
"type": "version",
|
||||
"_id": representation["parent"]
|
||||
})
|
||||
|
||||
# get all versions in list
|
||||
versions = io.find({
|
||||
"type": "version",
|
||||
"parent": version["parent"]
|
||||
}).distinct('name')
|
||||
|
||||
max_version = max(versions)
|
||||
|
||||
version_data = version.get("data", {})
|
||||
|
||||
orig_first = version_data.get("frameStart")
|
||||
orig_last = version_data.get("frameEnd")
|
||||
diff = orig_first - 1
|
||||
|
||||
# set first to 1
|
||||
first = orig_first - diff
|
||||
last = orig_last - diff
|
||||
self.handle_start = version_data.get("handleStart", 0)
|
||||
self.handle_end = version_data.get("handleEnd", 0)
|
||||
colorspace = version_data.get("colorspace")
|
||||
|
||||
if first is None:
|
||||
self.log.warning((
|
||||
"Missing start frame for updated version"
|
||||
"assuming starts at frame 0 for: "
|
||||
"{} ({})").format(
|
||||
read_node['name'].value(), representation))
|
||||
first = 0
|
||||
|
||||
# create handles offset (only to last, because of mov)
|
||||
last += self.handle_start + self.handle_end
|
||||
|
||||
read_node["file"].setValue(file)
|
||||
|
||||
# Set the global in to the start frame of the sequence
|
||||
read_node["origfirst"].setValue(first)
|
||||
read_node["first"].setValue(first)
|
||||
read_node["origlast"].setValue(last)
|
||||
read_node["last"].setValue(last)
|
||||
read_node['frame_mode'].setValue("start at")
|
||||
|
||||
if int(float(self.first_frame)) == int(
|
||||
float(read_node['frame'].value())):
|
||||
# start at workfile start
|
||||
read_node['frame'].setValue(str(self.first_frame))
|
||||
else:
|
||||
# start at version frame start
|
||||
read_node['frame'].setValue(str(orig_first - self.handle_start))
|
||||
|
||||
if colorspace:
|
||||
read_node["colorspace"].setValue(str(colorspace))
|
||||
|
||||
preset_clrsp = get_imageio_input_colorspace(file)
|
||||
|
||||
if preset_clrsp is not None:
|
||||
read_node["colorspace"].setValue(preset_clrsp)
|
||||
|
||||
updated_dict = {}
|
||||
updated_dict.update({
|
||||
"representation": str(representation["_id"]),
|
||||
"frameStart": str(first),
|
||||
"frameEnd": str(last),
|
||||
"version": str(version.get("name")),
|
||||
"colorspace": version_data.get("colorspace"),
|
||||
"source": version_data.get("source"),
|
||||
"handleStart": str(self.handle_start),
|
||||
"handleEnd": str(self.handle_end),
|
||||
"fps": str(version_data.get("fps")),
|
||||
"author": version_data.get("author"),
|
||||
"outputDir": version_data.get("outputDir")
|
||||
})
|
||||
|
||||
# change color of node
|
||||
if version.get("name") not in [max_version]:
|
||||
read_node["tile_color"].setValue(int("0xd84f20ff", 16))
|
||||
else:
|
||||
read_node["tile_color"].setValue(int("0x4ecd25ff", 16))
|
||||
|
||||
if version_data.get("retime", None):
|
||||
speed = version_data.get("speed", 1)
|
||||
time_warp_nodes = version_data.get("timewarps", [])
|
||||
self.make_retimes(speed, time_warp_nodes)
|
||||
|
||||
# Update the imprinted representation
|
||||
update_container(
|
||||
read_node, updated_dict
|
||||
)
|
||||
self.log.info("udated to version: {}".format(version.get("name")))
|
||||
|
||||
def remove(self, container):
|
||||
|
||||
from avalon.nuke import viewer_update_and_undo_stop
|
||||
|
||||
read_node = nuke.toNode(container['objectName'])
|
||||
assert read_node.Class() == "Read", "Must be Read"
|
||||
|
||||
with viewer_update_and_undo_stop():
|
||||
nuke.delete(read_node)
|
||||
|
||||
def make_retimes(self, speed, time_warp_nodes):
|
||||
''' Create all retime and timewarping nodes with coppied animation '''
|
||||
if speed != 1:
|
||||
rtn = nuke.createNode(
|
||||
"Retime",
|
||||
"speed {}".format(speed))
|
||||
rtn["before"].setValue("continue")
|
||||
rtn["after"].setValue("continue")
|
||||
rtn["input.first_lock"].setValue(True)
|
||||
rtn["input.first"].setValue(
|
||||
self.first_frame
|
||||
)
|
||||
|
||||
if time_warp_nodes != []:
|
||||
start_anim = self.first_frame + (self.handle_start / speed)
|
||||
for timewarp in time_warp_nodes:
|
||||
twn = nuke.createNode(timewarp["Class"],
|
||||
"name {}".format(timewarp["name"]))
|
||||
if isinstance(timewarp["lookup"], list):
|
||||
# if array for animation
|
||||
twn["lookup"].setAnimated()
|
||||
for i, value in enumerate(timewarp["lookup"]):
|
||||
twn["lookup"].setValueAt(
|
||||
(start_anim + i) + value,
|
||||
(start_anim + i))
|
||||
else:
|
||||
# if static value `int`
|
||||
twn["lookup"].setValue(timewarp["lookup"])
|
||||
|
|
@ -1,320 +0,0 @@
|
|||
import nuke
|
||||
from avalon.vendor import qargparse
|
||||
from avalon import api, io
|
||||
from openpype.hosts.nuke.api.lib import (
|
||||
get_imageio_input_colorspace
|
||||
)
|
||||
|
||||
|
||||
class LoadSequence(api.Loader):
|
||||
"""Load image sequence into Nuke"""
|
||||
|
||||
families = ["render", "source", "plate", "review"]
|
||||
representations = ["exr", "dpx"]
|
||||
|
||||
label = "Load Image Sequence"
|
||||
order = -20
|
||||
icon = "file-video-o"
|
||||
color = "white"
|
||||
|
||||
script_start = nuke.root()["first_frame"].value()
|
||||
|
||||
# option gui
|
||||
defaults = {
|
||||
"start_at_workfile": True
|
||||
}
|
||||
|
||||
options = [
|
||||
qargparse.Boolean(
|
||||
"start_at_workfile",
|
||||
help="Load at workfile start frame",
|
||||
default=True
|
||||
)
|
||||
]
|
||||
|
||||
node_name_template = "{class_name}_{ext}"
|
||||
|
||||
def load(self, context, name, namespace, options):
|
||||
from avalon.nuke import (
|
||||
containerise,
|
||||
viewer_update_and_undo_stop
|
||||
)
|
||||
|
||||
start_at_workfile = options.get(
|
||||
"start_at_workfile", self.defaults["start_at_workfile"])
|
||||
|
||||
version = context['version']
|
||||
version_data = version.get("data", {})
|
||||
repr_id = context["representation"]["_id"]
|
||||
|
||||
self.log.info("version_data: {}\n".format(version_data))
|
||||
self.log.debug(
|
||||
"Representation id `{}` ".format(repr_id))
|
||||
|
||||
self.first_frame = int(nuke.root()["first_frame"].getValue())
|
||||
self.handle_start = version_data.get("handleStart", 0)
|
||||
self.handle_end = version_data.get("handleEnd", 0)
|
||||
|
||||
first = version_data.get("frameStart", None)
|
||||
last = version_data.get("frameEnd", None)
|
||||
|
||||
# Fallback to asset name when namespace is None
|
||||
if namespace is None:
|
||||
namespace = context['asset']['name']
|
||||
|
||||
first -= self.handle_start
|
||||
last += self.handle_end
|
||||
|
||||
file = self.fname
|
||||
|
||||
if not file:
|
||||
repr_id = context["representation"]["_id"]
|
||||
self.log.warning(
|
||||
"Representation id `{}` is failing to load".format(repr_id))
|
||||
return
|
||||
|
||||
file = file.replace("\\", "/")
|
||||
|
||||
repr_cont = context["representation"]["context"]
|
||||
assert repr_cont.get("frame"), "Representation is not sequence"
|
||||
|
||||
if "#" not in file:
|
||||
frame = repr_cont.get("frame")
|
||||
if frame:
|
||||
padding = len(frame)
|
||||
file = file.replace(frame, "#" * padding)
|
||||
|
||||
name_data = {
|
||||
"asset": repr_cont["asset"],
|
||||
"subset": repr_cont["subset"],
|
||||
"representation": context["representation"]["name"],
|
||||
"ext": repr_cont["representation"],
|
||||
"id": context["representation"]["_id"],
|
||||
"class_name": self.__class__.__name__
|
||||
}
|
||||
|
||||
read_name = self.node_name_template.format(**name_data)
|
||||
|
||||
# Create the Loader with the filename path set
|
||||
read_node = nuke.createNode(
|
||||
"Read",
|
||||
"name {}".format(read_name))
|
||||
|
||||
# to avoid multiple undo steps for rest of process
|
||||
# we will switch off undo-ing
|
||||
with viewer_update_and_undo_stop():
|
||||
read_node["file"].setValue(file)
|
||||
|
||||
# Set colorspace defined in version data
|
||||
colorspace = context["version"]["data"].get("colorspace")
|
||||
if colorspace:
|
||||
read_node["colorspace"].setValue(str(colorspace))
|
||||
|
||||
preset_clrsp = get_imageio_input_colorspace(file)
|
||||
|
||||
if preset_clrsp is not None:
|
||||
read_node["colorspace"].setValue(preset_clrsp)
|
||||
|
||||
# set start frame depending on workfile or version
|
||||
self.loader_shift(read_node, start_at_workfile)
|
||||
read_node["origfirst"].setValue(int(first))
|
||||
read_node["first"].setValue(int(first))
|
||||
read_node["origlast"].setValue(int(last))
|
||||
read_node["last"].setValue(int(last))
|
||||
|
||||
# add additional metadata from the version to imprint Avalon knob
|
||||
add_keys = ["frameStart", "frameEnd",
|
||||
"source", "colorspace", "author", "fps", "version",
|
||||
"handleStart", "handleEnd"]
|
||||
|
||||
data_imprint = {}
|
||||
for k in add_keys:
|
||||
if k == 'version':
|
||||
data_imprint.update({k: context["version"]['name']})
|
||||
else:
|
||||
data_imprint.update(
|
||||
{k: context["version"]['data'].get(k, str(None))})
|
||||
|
||||
data_imprint.update({"objectName": read_name})
|
||||
|
||||
read_node["tile_color"].setValue(int("0x4ecd25ff", 16))
|
||||
|
||||
if version_data.get("retime", None):
|
||||
speed = version_data.get("speed", 1)
|
||||
time_warp_nodes = version_data.get("timewarps", [])
|
||||
self.make_retimes(speed, time_warp_nodes)
|
||||
|
||||
return containerise(read_node,
|
||||
name=name,
|
||||
namespace=namespace,
|
||||
context=context,
|
||||
loader=self.__class__.__name__,
|
||||
data=data_imprint)
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
||||
def update(self, container, representation):
|
||||
"""Update the Loader's path
|
||||
|
||||
Nuke automatically tries to reset some variables when changing
|
||||
the loader's path to a new file. These automatic changes are to its
|
||||
inputs:
|
||||
|
||||
"""
|
||||
|
||||
from avalon.nuke import (
|
||||
update_container
|
||||
)
|
||||
|
||||
read_node = nuke.toNode(container['objectName'])
|
||||
|
||||
assert read_node.Class() == "Read", "Must be Read"
|
||||
|
||||
repr_cont = representation["context"]
|
||||
assert repr_cont.get("frame"), "Representation is not sequence"
|
||||
|
||||
file = api.get_representation_path(representation)
|
||||
|
||||
if not file:
|
||||
repr_id = representation["_id"]
|
||||
self.log.warning(
|
||||
"Representation id `{}` is failing to load".format(repr_id))
|
||||
return
|
||||
|
||||
file = file.replace("\\", "/")
|
||||
|
||||
if "#" not in file:
|
||||
frame = repr_cont.get("frame")
|
||||
if frame:
|
||||
padding = len(frame)
|
||||
file = file.replace(frame, "#" * padding)
|
||||
|
||||
# Get start frame from version data
|
||||
version = io.find_one({
|
||||
"type": "version",
|
||||
"_id": representation["parent"]
|
||||
})
|
||||
|
||||
# get all versions in list
|
||||
versions = io.find({
|
||||
"type": "version",
|
||||
"parent": version["parent"]
|
||||
}).distinct('name')
|
||||
|
||||
max_version = max(versions)
|
||||
|
||||
version_data = version.get("data", {})
|
||||
|
||||
self.first_frame = int(nuke.root()["first_frame"].getValue())
|
||||
self.handle_start = version_data.get("handleStart", 0)
|
||||
self.handle_end = version_data.get("handleEnd", 0)
|
||||
|
||||
first = version_data.get("frameStart")
|
||||
last = version_data.get("frameEnd")
|
||||
|
||||
if first is None:
|
||||
self.log.warning(
|
||||
"Missing start frame for updated version"
|
||||
"assuming starts at frame 0 for: "
|
||||
"{} ({})".format(read_node['name'].value(), representation))
|
||||
first = 0
|
||||
|
||||
first -= self.handle_start
|
||||
last += self.handle_end
|
||||
|
||||
read_node["file"].setValue(file)
|
||||
|
||||
# set start frame depending on workfile or version
|
||||
self.loader_shift(
|
||||
read_node,
|
||||
bool("start at" in read_node['frame_mode'].value()))
|
||||
|
||||
read_node["origfirst"].setValue(int(first))
|
||||
read_node["first"].setValue(int(first))
|
||||
read_node["origlast"].setValue(int(last))
|
||||
read_node["last"].setValue(int(last))
|
||||
|
||||
updated_dict = {}
|
||||
updated_dict.update({
|
||||
"representation": str(representation["_id"]),
|
||||
"frameStart": str(first),
|
||||
"frameEnd": str(last),
|
||||
"version": str(version.get("name")),
|
||||
"colorspace": version_data.get("colorspace"),
|
||||
"source": version_data.get("source"),
|
||||
"handleStart": str(self.handle_start),
|
||||
"handleEnd": str(self.handle_end),
|
||||
"fps": str(version_data.get("fps")),
|
||||
"author": version_data.get("author"),
|
||||
"outputDir": version_data.get("outputDir"),
|
||||
})
|
||||
|
||||
# change color of read_node
|
||||
if version.get("name") not in [max_version]:
|
||||
read_node["tile_color"].setValue(int("0xd84f20ff", 16))
|
||||
else:
|
||||
read_node["tile_color"].setValue(int("0x4ecd25ff", 16))
|
||||
|
||||
if version_data.get("retime", None):
|
||||
speed = version_data.get("speed", 1)
|
||||
time_warp_nodes = version_data.get("timewarps", [])
|
||||
self.make_retimes(speed, time_warp_nodes)
|
||||
|
||||
# Update the imprinted representation
|
||||
update_container(
|
||||
read_node,
|
||||
updated_dict
|
||||
)
|
||||
self.log.info("udated to version: {}".format(version.get("name")))
|
||||
|
||||
def remove(self, container):
|
||||
|
||||
from avalon.nuke import viewer_update_and_undo_stop
|
||||
|
||||
read_node = nuke.toNode(container['objectName'])
|
||||
assert read_node.Class() == "Read", "Must be Read"
|
||||
|
||||
with viewer_update_and_undo_stop():
|
||||
nuke.delete(read_node)
|
||||
|
||||
def make_retimes(self, speed, time_warp_nodes):
|
||||
''' Create all retime and timewarping nodes with coppied animation '''
|
||||
if speed != 1:
|
||||
rtn = nuke.createNode(
|
||||
"Retime",
|
||||
"speed {}".format(speed))
|
||||
rtn["before"].setValue("continue")
|
||||
rtn["after"].setValue("continue")
|
||||
rtn["input.first_lock"].setValue(True)
|
||||
rtn["input.first"].setValue(
|
||||
self.first_frame
|
||||
)
|
||||
|
||||
if time_warp_nodes != []:
|
||||
start_anim = self.first_frame + (self.handle_start / speed)
|
||||
for timewarp in time_warp_nodes:
|
||||
twn = nuke.createNode(timewarp["Class"],
|
||||
"name {}".format(timewarp["name"]))
|
||||
if isinstance(timewarp["lookup"], list):
|
||||
# if array for animation
|
||||
twn["lookup"].setAnimated()
|
||||
for i, value in enumerate(timewarp["lookup"]):
|
||||
twn["lookup"].setValueAt(
|
||||
(start_anim + i) + value,
|
||||
(start_anim + i))
|
||||
else:
|
||||
# if static value `int`
|
||||
twn["lookup"].setValue(timewarp["lookup"])
|
||||
|
||||
def loader_shift(self, read_node, workfile_start=False):
|
||||
""" Set start frame of read node to a workfile start
|
||||
|
||||
Args:
|
||||
read_node (nuke.Node): The nuke's read node
|
||||
workfile_start (bool): set workfile start frame if true
|
||||
|
||||
"""
|
||||
if workfile_start:
|
||||
read_node['frame_mode'].setValue("start at")
|
||||
read_node['frame'].setValue(str(self.script_start))
|
||||
|
|
@ -56,8 +56,8 @@ class ValidateOutputResolution(pyblish.api.InstancePlugin):
|
|||
|
||||
def process(self, instance):
|
||||
|
||||
# Skip bounding box check if a crop node exists.
|
||||
if instance[0].dependencies()[0].Class() == "Crop":
|
||||
# Skip bounding box check if a reformat node exists.
|
||||
if instance[0].dependencies()[0].Class() == "Reformat":
|
||||
return
|
||||
|
||||
msg = "Bounding box is outside the format."
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ class ExtractReview(openpype.api.Extractor):
|
|||
hosts = ["photoshop"]
|
||||
families = ["review"]
|
||||
|
||||
# Extract Options
|
||||
jpg_options = None
|
||||
mov_options = None
|
||||
|
||||
def process(self, instance):
|
||||
staging_dir = self.staging_dir(instance)
|
||||
self.log.info("Outputting image to {}".format(staging_dir))
|
||||
|
|
@ -53,7 +57,8 @@ class ExtractReview(openpype.api.Extractor):
|
|||
"name": "jpg",
|
||||
"ext": "jpg",
|
||||
"files": output_image,
|
||||
"stagingDir": staging_dir
|
||||
"stagingDir": staging_dir,
|
||||
"tags": self.jpg_options['tags']
|
||||
})
|
||||
instance.data["stagingDir"] = staging_dir
|
||||
|
||||
|
|
@ -97,7 +102,7 @@ class ExtractReview(openpype.api.Extractor):
|
|||
"frameEnd": 1,
|
||||
"fps": 25,
|
||||
"preview": True,
|
||||
"tags": ["review", "ftrackreview"]
|
||||
"tags": self.mov_options['tags']
|
||||
})
|
||||
|
||||
# Required for extract_review plugin (L222 onwards).
|
||||
|
|
|
|||
|
|
@ -8,15 +8,7 @@ from .pipeline import (
|
|||
launch_workfiles_app
|
||||
)
|
||||
|
||||
from avalon.tools import (
|
||||
creator,
|
||||
sceneinventory,
|
||||
subsetmanager
|
||||
)
|
||||
from openpype.tools import (
|
||||
loader,
|
||||
libraryloader,
|
||||
)
|
||||
from openpype.tools.utils import host_tools
|
||||
|
||||
|
||||
def load_stylesheet():
|
||||
|
|
@ -32,7 +24,7 @@ def load_stylesheet():
|
|||
|
||||
class Spacer(QtWidgets.QWidget):
|
||||
def __init__(self, height, *args, **kwargs):
|
||||
super(self.__class__, self).__init__(*args, **kwargs)
|
||||
super(Spacer, self).__init__(*args, **kwargs)
|
||||
|
||||
self.setFixedHeight(height)
|
||||
|
||||
|
|
@ -49,7 +41,7 @@ class Spacer(QtWidgets.QWidget):
|
|||
|
||||
class OpenPypeMenu(QtWidgets.QWidget):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(self.__class__, self).__init__(*args, **kwargs)
|
||||
super(OpenPypeMenu, self).__init__(*args, **kwargs)
|
||||
|
||||
self.setObjectName("OpenPypeMenu")
|
||||
|
||||
|
|
@ -119,7 +111,7 @@ class OpenPypeMenu(QtWidgets.QWidget):
|
|||
|
||||
def on_create_clicked(self):
|
||||
print("Clicked Create")
|
||||
creator.show()
|
||||
host_tools.show_creator()
|
||||
|
||||
def on_publish_clicked(self):
|
||||
print("Clicked Publish")
|
||||
|
|
@ -127,19 +119,19 @@ class OpenPypeMenu(QtWidgets.QWidget):
|
|||
|
||||
def on_load_clicked(self):
|
||||
print("Clicked Load")
|
||||
loader.show(use_context=True)
|
||||
host_tools.show_loader(use_context=True)
|
||||
|
||||
def on_inventory_clicked(self):
|
||||
print("Clicked Inventory")
|
||||
sceneinventory.show()
|
||||
host_tools.show_scene_inventory()
|
||||
|
||||
def on_subsetm_clicked(self):
|
||||
print("Clicked Subset Manager")
|
||||
subsetmanager.show()
|
||||
host_tools.show_subset_manager()
|
||||
|
||||
def on_libload_clicked(self):
|
||||
print("Clicked Library")
|
||||
libraryloader.show()
|
||||
host_tools.show_library_loader()
|
||||
|
||||
def on_rename_clicked(self):
|
||||
print("Clicked Rename")
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ Basic avalon integration
|
|||
import os
|
||||
import contextlib
|
||||
from collections import OrderedDict
|
||||
from openpype.tools import workfiles
|
||||
from avalon import api as avalon
|
||||
from avalon import schema
|
||||
from avalon.pipeline import AVALON_CONTAINER_ID
|
||||
|
|
@ -12,6 +11,7 @@ from pyblish import api as pyblish
|
|||
from openpype.api import Logger
|
||||
from . import lib
|
||||
from . import PLUGINS_DIR
|
||||
from openpype.tools.utils import host_tools
|
||||
log = Logger().get_logger(__name__)
|
||||
|
||||
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
||||
|
|
@ -212,14 +212,12 @@ def update_container(timeline_item, data=None):
|
|||
|
||||
|
||||
def launch_workfiles_app(*args):
|
||||
workdir = os.environ["AVALON_WORKDIR"]
|
||||
workfiles.show(workdir)
|
||||
host_tools.show_workfiles()
|
||||
|
||||
|
||||
def publish(parent):
|
||||
"""Shorthand to publish from within host"""
|
||||
from avalon.tools import publish
|
||||
return publish.show(parent)
|
||||
return host_tools.show_publish()
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import logging
|
|||
import re
|
||||
import json
|
||||
import tempfile
|
||||
import distutils
|
||||
|
||||
from .execute import run_subprocess
|
||||
from .profiles_filtering import filter_profiles
|
||||
|
|
@ -377,7 +378,7 @@ def oiio_supported():
|
|||
"""
|
||||
Checks if oiiotool is configured for this platform.
|
||||
|
||||
Expects full path to executable.
|
||||
Triggers simple subprocess, handles exception if fails.
|
||||
|
||||
'should_decompress' will throw exception if configured,
|
||||
but not present or not working.
|
||||
|
|
@ -385,7 +386,10 @@ def oiio_supported():
|
|||
(bool)
|
||||
"""
|
||||
oiio_path = get_oiio_tools_path()
|
||||
if not oiio_path or not os.path.exists(oiio_path):
|
||||
if oiio_path:
|
||||
oiio_path = distutils.spawn.find_executable(oiio_path)
|
||||
|
||||
if not oiio_path:
|
||||
log.debug("OIIOTool is not configured or not present at {}".
|
||||
format(oiio_path))
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class _ModuleClass(object):
|
|||
|
||||
def __getattr__(self, attr_name):
|
||||
if attr_name not in self.__attributes__:
|
||||
if attr_name in ("__path__"):
|
||||
if attr_name in ("__path__", "__file__"):
|
||||
return None
|
||||
raise ImportError("No module named {}.{}".format(
|
||||
self.name, attr_name
|
||||
|
|
@ -104,6 +104,9 @@ class _InterfacesClass(_ModuleClass):
|
|||
"""
|
||||
def __getattr__(self, attr_name):
|
||||
if attr_name not in self.__attributes__:
|
||||
if attr_name in ("__path__", "__file__"):
|
||||
return None
|
||||
|
||||
# Fake Interface if is not missing
|
||||
self.__attributes__[attr_name] = type(
|
||||
attr_name,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import pyblish.api
|
|||
class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin):
|
||||
"""Collect Deadline Webservice URL from instance."""
|
||||
|
||||
order = pyblish.api.CollectorOrder + 0.01
|
||||
order = pyblish.api.CollectorOrder + 0.02
|
||||
label = "Deadline Webservice from the Instance"
|
||||
families = ["rendering"]
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import pyblish.api
|
|||
class CollectDefaultDeadlineServer(pyblish.api.ContextPlugin):
|
||||
"""Collect default Deadline Webservice URL."""
|
||||
|
||||
order = pyblish.api.CollectorOrder
|
||||
order = pyblish.api.CollectorOrder + 0.01
|
||||
label = "Default Deadline Webservice"
|
||||
|
||||
def process(self, context):
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import json
|
|||
from avalon.api import AvalonMongoDB
|
||||
from openpype.api import ProjectSettings
|
||||
from openpype.lib import create_project
|
||||
from openpype.settings import SaveWarningExc
|
||||
|
||||
from openpype_modules.ftrack.lib import (
|
||||
ServerAction,
|
||||
|
|
@ -312,7 +313,6 @@ class PrepareProjectServer(ServerAction):
|
|||
if not in_data:
|
||||
return
|
||||
|
||||
|
||||
root_values = {}
|
||||
root_key = "__root__"
|
||||
for key in tuple(in_data.keys()):
|
||||
|
|
@ -392,7 +392,12 @@ class PrepareProjectServer(ServerAction):
|
|||
else:
|
||||
attributes_entity[key] = value
|
||||
|
||||
project_settings.save()
|
||||
try:
|
||||
project_settings.save()
|
||||
except SaveWarningExc as exc:
|
||||
self.log.info("Few warnings happened during settings save:")
|
||||
for warning in exc.warnings:
|
||||
self.log.info(str(warning))
|
||||
|
||||
# Change custom attributes on project
|
||||
if custom_attribute_values:
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import json
|
|||
from avalon.api import AvalonMongoDB
|
||||
from openpype.api import ProjectSettings
|
||||
from openpype.lib import create_project
|
||||
from openpype.settings import SaveWarningExc
|
||||
|
||||
from openpype_modules.ftrack.lib import (
|
||||
BaseAction,
|
||||
|
|
@ -417,7 +418,12 @@ class PrepareProjectLocal(BaseAction):
|
|||
else:
|
||||
attributes_entity[key] = value
|
||||
|
||||
project_settings.save()
|
||||
try:
|
||||
project_settings.save()
|
||||
except SaveWarningExc as exc:
|
||||
self.log.info("Few warnings happened during settings save:")
|
||||
for warning in exc.warnings:
|
||||
self.log.info(str(warning))
|
||||
|
||||
# Change custom attributes on project
|
||||
if custom_attribute_values:
|
||||
|
|
|
|||
|
|
@ -371,7 +371,7 @@ class FtrackModule(
|
|||
return self.tray_module.validate()
|
||||
|
||||
def tray_exit(self):
|
||||
return self.tray_module.stop_action_server()
|
||||
self.tray_module.tray_exit()
|
||||
|
||||
def set_credentials_to_env(self, username, api_key):
|
||||
os.environ["FTRACK_API_USER"] = username or ""
|
||||
|
|
|
|||
|
|
@ -289,6 +289,10 @@ class FtrackTrayWrapper:
|
|||
|
||||
parent_menu.addMenu(tray_menu)
|
||||
|
||||
def tray_exit(self):
|
||||
self.stop_action_server()
|
||||
self.stop_timer_thread()
|
||||
|
||||
# Definition of visibility of each menu actions
|
||||
def set_menu_visibility(self):
|
||||
self.tray_server_menu.menuAction().setVisible(self.bool_logged)
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@ class TimersManager(OpenPypeModule, ITrayService):
|
|||
def tray_exit(self):
|
||||
if self._idle_manager:
|
||||
self._idle_manager.stop()
|
||||
self._idle_manager.wait()
|
||||
|
||||
def start_timer(self, project_name, asset_name, task_name, hierarchy):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -68,6 +68,12 @@ class CollectResourcesPath(pyblish.api.InstancePlugin):
|
|||
"representation": "TEMP"
|
||||
})
|
||||
|
||||
# For the first time publish
|
||||
if instance.data.get("hierarchy"):
|
||||
template_data.update({
|
||||
"hierarchy": instance.data["hierarchy"]
|
||||
})
|
||||
|
||||
anatomy_filled = anatomy.format(template_data)
|
||||
|
||||
if "folder" in anatomy.templates["publish"]:
|
||||
|
|
|
|||
|
|
@ -45,7 +45,8 @@ class ExtractBurnin(openpype.api.Extractor):
|
|||
"aftereffects",
|
||||
"tvpaint",
|
||||
"webpublisher",
|
||||
"aftereffects"
|
||||
"aftereffects",
|
||||
"photoshop"
|
||||
# "resolve"
|
||||
]
|
||||
optional = True
|
||||
|
|
|
|||
|
|
@ -109,9 +109,7 @@ def _prores_codec_args(ffprobe_data, source_ffmpeg_cmd):
|
|||
|
||||
|
||||
def _h264_codec_args(ffprobe_data, source_ffmpeg_cmd):
|
||||
output = []
|
||||
|
||||
output.extend(["-codec:v", "h264"])
|
||||
output = ["-codec:v", "h264"]
|
||||
|
||||
# Use arguments from source if are available source arguments
|
||||
if source_ffmpeg_cmd:
|
||||
|
|
@ -137,6 +135,32 @@ def _h264_codec_args(ffprobe_data, source_ffmpeg_cmd):
|
|||
return output
|
||||
|
||||
|
||||
def _dnxhd_codec_args(ffprobe_data, source_ffmpeg_cmd):
|
||||
output = ["-codec:v", "dnxhd"]
|
||||
|
||||
# Use source profile (profiles in metadata are not usable in args directly)
|
||||
profile = ffprobe_data.get("profile") or ""
|
||||
# Lower profile and replace space with underscore
|
||||
cleaned_profile = profile.lower().replace(" ", "_")
|
||||
dnx_profiles = {
|
||||
"dnxhd",
|
||||
"dnxhr_lb",
|
||||
"dnxhr_sq",
|
||||
"dnxhr_hq",
|
||||
"dnxhr_hqx",
|
||||
"dnxhr_444"
|
||||
}
|
||||
if cleaned_profile in dnx_profiles:
|
||||
output.extend(["-profile:v", cleaned_profile])
|
||||
|
||||
pix_fmt = ffprobe_data.get("pix_fmt")
|
||||
if pix_fmt:
|
||||
output.extend(["-pix_fmt", pix_fmt])
|
||||
|
||||
output.extend(["-g", "1"])
|
||||
return output
|
||||
|
||||
|
||||
def get_codec_args(ffprobe_data, source_ffmpeg_cmd):
|
||||
codec_name = ffprobe_data.get("codec_name")
|
||||
# Codec "prores"
|
||||
|
|
@ -147,6 +171,10 @@ def get_codec_args(ffprobe_data, source_ffmpeg_cmd):
|
|||
if codec_name == "h264":
|
||||
return _h264_codec_args(ffprobe_data, source_ffmpeg_cmd)
|
||||
|
||||
# Coded DNxHD
|
||||
if codec_name == "dnxhd":
|
||||
return _dnxhd_codec_args(ffprobe_data, source_ffmpeg_cmd)
|
||||
|
||||
output = []
|
||||
if codec_name:
|
||||
output.extend(["-codec:v", codec_name])
|
||||
|
|
|
|||
|
|
@ -315,6 +315,21 @@
|
|||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ValidateRigContents": {
|
||||
"enabled": false,
|
||||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ValidateRigJointsHidden": {
|
||||
"enabled": false,
|
||||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ValidateRigControllers": {
|
||||
"enabled": false,
|
||||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ValidateCameraAttributes": {
|
||||
"enabled": false,
|
||||
"optional": true,
|
||||
|
|
|
|||
|
|
@ -117,16 +117,7 @@
|
|||
"load": {
|
||||
"LoadImage": {
|
||||
"enabled": true,
|
||||
"families": [
|
||||
"render2d",
|
||||
"source",
|
||||
"plate",
|
||||
"render",
|
||||
"prerender",
|
||||
"review",
|
||||
"image"
|
||||
],
|
||||
"representations": [
|
||||
"_representations": [
|
||||
"exr",
|
||||
"dpx",
|
||||
"jpg",
|
||||
|
|
@ -137,39 +128,9 @@
|
|||
],
|
||||
"node_name_template": "{class_name}_{ext}"
|
||||
},
|
||||
"LoadMov": {
|
||||
"LoadClip": {
|
||||
"enabled": true,
|
||||
"families": [
|
||||
"source",
|
||||
"plate",
|
||||
"render",
|
||||
"prerender",
|
||||
"review"
|
||||
],
|
||||
"representations": [
|
||||
"mov",
|
||||
"review",
|
||||
"mp4",
|
||||
"h264"
|
||||
],
|
||||
"node_name_template": "{class_name}_{ext}"
|
||||
},
|
||||
"LoadSequence": {
|
||||
"enabled": true,
|
||||
"families": [
|
||||
"render2d",
|
||||
"source",
|
||||
"plate",
|
||||
"render",
|
||||
"prerender",
|
||||
"review"
|
||||
],
|
||||
"representations": [
|
||||
"exr",
|
||||
"dpx",
|
||||
"jpg",
|
||||
"jpeg",
|
||||
"png"
|
||||
"_representations": [
|
||||
],
|
||||
"node_name_template": "{class_name}_{ext}"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,18 @@
|
|||
"png",
|
||||
"jpg"
|
||||
]
|
||||
},
|
||||
"ExtractReview": {
|
||||
"jpg_options": {
|
||||
"tags": [
|
||||
]
|
||||
},
|
||||
"mov_options": {
|
||||
"tags": [
|
||||
"review",
|
||||
"ftrackreview"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"workfile_builder": {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Basic rules
|
||||
- configurations does not define GUI, but GUI defines configurations!
|
||||
- output is always json (yaml is not needed for anatomy templates anymore)
|
||||
- output is always json serializable
|
||||
- GUI schema has multiple input types, all inputs are represented by a dictionary
|
||||
- each input may have "input modifiers" (keys in dictionary) that are required or optional
|
||||
- only required modifier for all input items is key `"type"` which says what type of item it is
|
||||
|
|
@ -13,16 +13,16 @@
|
|||
- `"is_group"` - define that all values under key in hierarchy will be overriden if any value is modified, this information is also stored to overrides
|
||||
- this keys is not allowed for all inputs as they may have not reason for that
|
||||
- key is validated, can be only once in hierarchy but is not required
|
||||
- currently there are `system configurations` and `project configurations`
|
||||
- currently there are `system settings` and `project settings`
|
||||
|
||||
## Inner schema
|
||||
- GUI schemas are huge json files, to be able to split whole configuration into multiple schema there's type `schema`
|
||||
- system configuration schemas are stored in `~/tools/settings/settings/gui_schemas/system_schema/` and project configurations in `~/tools/settings/settings/gui_schemas/projects_schema/`
|
||||
- system configuration schemas are stored in `~/openpype/settings/entities/schemas/system_schema/` and project configurations in `~/openpype/settings/entities/schemas/projects_schema/`
|
||||
- each schema name is filename of json file except extension (without ".json")
|
||||
- if content is dictionary content will be used as `schema` else will be used as `schema_template`
|
||||
|
||||
### schema
|
||||
- can have only key `"children"` which is list of strings, each string should represent another schema (order matters) string represebts name of the schema
|
||||
- can have only key `"children"` which is list of strings, each string should represent another schema (order matters) string represents name of the schema
|
||||
- will just paste schemas from other schema file in order of "children" list
|
||||
|
||||
```
|
||||
|
|
@ -32,8 +32,9 @@
|
|||
}
|
||||
```
|
||||
|
||||
### schema_template
|
||||
### template
|
||||
- allows to define schema "templates" to not duplicate same content multiple times
|
||||
- legacy name is `schema_template` (still usable)
|
||||
```javascript
|
||||
// EXAMPLE json file content (filename: example_template.json)
|
||||
[
|
||||
|
|
@ -59,11 +60,11 @@
|
|||
// EXAMPLE usage of the template in schema
|
||||
{
|
||||
"type": "dict",
|
||||
"key": "schema_template_examples",
|
||||
"key": "template_examples",
|
||||
"label": "Schema template examples",
|
||||
"children": [
|
||||
{
|
||||
"type": "schema_template",
|
||||
"type": "template",
|
||||
// filename of template (example_template.json)
|
||||
"name": "example_template",
|
||||
"template_data": {
|
||||
|
|
@ -72,7 +73,7 @@
|
|||
"multipath_executables": false
|
||||
}
|
||||
}, {
|
||||
"type": "schema_template",
|
||||
"type": "template",
|
||||
"name": "example_template",
|
||||
"template_data": {
|
||||
"host_label": "Maya 2020",
|
||||
|
|
@ -98,8 +99,16 @@
|
|||
...
|
||||
}
|
||||
```
|
||||
- Unfilled fields can be also used for non string values, in that case value must contain only one key and value for fill must contain right type.
|
||||
- Unfilled fields can be also used for non string values(e.g. dictionary), in that case value must contain only one key and value for fill must contain right type.
|
||||
```javascript
|
||||
// Passed data
|
||||
{
|
||||
"executable_multiplatform": {
|
||||
"type": "schema",
|
||||
"name": "my_multiplatform_schema"
|
||||
}
|
||||
}
|
||||
// Template content
|
||||
{
|
||||
...
|
||||
// Allowed
|
||||
|
|
@ -121,32 +130,34 @@
|
|||
"name": "project_settings/global"
|
||||
}
|
||||
```
|
||||
- all valid `ModuleSettingsDef` classes where calling of `get_settings_schemas`
|
||||
- all valid `BaseModuleSettingsDef` classes where calling of `get_settings_schemas`
|
||||
will return dictionary where is key "project_settings/global" with schemas
|
||||
will extend and replace this item
|
||||
- works almost the same way as templates
|
||||
- dynamic schemas work almost the same way as templates
|
||||
- one item can be replaced by multiple items (or by 0 items)
|
||||
- goal is to dynamically loaded settings of OpenPype addons without having
|
||||
their schemas or default values in main repository
|
||||
- values of these schemas are saved using the `BaseModuleSettingsDef` methods
|
||||
- easiest is to use `JsonFilesSettingsDef` which has full implementation of storing default values to json files all you have to implement is method `get_settings_root_path` which should return path to root directory where settings schema can be found and will be saved
|
||||
|
||||
## Basic Dictionary inputs
|
||||
- these inputs wraps another inputs into {key: value} relation
|
||||
|
||||
## dict
|
||||
- this is another dictionary input wrapping more inputs but visually makes them different
|
||||
- item may be used as widget (in `list` or `dict-modifiable`)
|
||||
- this is dictionary type wrapping more inputs with keys defined in schema
|
||||
- may be used as dynamic children (e.g. in `list` or `dict-modifiable`)
|
||||
- in that case the only key modifier is `children` which is list of it's keys
|
||||
- USAGE: e.g. List of dictionaries where each dictionary have same structure.
|
||||
- item may be with or without `"label"` if is not used as widget
|
||||
- required keys are `"key"` under which will be stored
|
||||
- without label it is just wrap item holding `"key"`
|
||||
- can't have `"is_group"` key set to True as it breaks visual override showing
|
||||
- if `"label"` is entetered there which will be shown in GUI
|
||||
- item with label can be collapsible
|
||||
- that can be set with key `"collapsible"` as `True`/`False` (Default: `True`)
|
||||
- with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`)
|
||||
- it is possible to add darker background with `"highlight_content"` (Default: `False`)
|
||||
- darker background has limits of maximum applies after 3-4 nested highlighted items there is not difference in the color
|
||||
- if is not used as dynamic children then must have defined `"key"` under which are it's values stored
|
||||
- may be with or without `"label"` (only for GUI)
|
||||
- `"label"` must be set to be able mark item as group with `"is_group"` key set to True
|
||||
- item with label can visually wrap it's children
|
||||
- this option is enabled by default to turn off set `"use_label_wrap"` to `False`
|
||||
- label wrap is by default collapsible
|
||||
- that can be set with key `"collapsible"` to `True`/`False`
|
||||
- with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`)
|
||||
- it is possible to add lighter background with `"highlight_content"` (Default: `False`)
|
||||
- lighter background has limits of maximum applies after 3-4 nested highlighted items there is not much difference in the color
|
||||
- output is dictionary `{the "key": children values}`
|
||||
```
|
||||
# Example
|
||||
|
|
@ -198,8 +209,8 @@
|
|||
```
|
||||
|
||||
## dict-conditional
|
||||
- is similar to `dict` but has only one child entity that will be always available
|
||||
- the one entity is enumerator of possible values and based on value of the entity are defined and used other children entities
|
||||
- is similar to `dict` but has always available one enum entity
|
||||
- the enum entity has single selection and it's value define other children entities
|
||||
- each value of enumerator have defined children that will be used
|
||||
- there is no way how to have shared entities across multiple enum items
|
||||
- value from enumerator is also stored next to other values
|
||||
|
|
@ -207,22 +218,27 @@
|
|||
- `enum_key` must match key regex and any enum item can't have children with same key
|
||||
- `enum_label` is label of the entity for UI purposes
|
||||
- enum items are define with `enum_children`
|
||||
- it's a list where each item represents enum item
|
||||
- it's a list where each item represents single item for the enum
|
||||
- all items in `enum_children` must have at least `key` key which represents value stored under `enum_key`
|
||||
- items can define `label` for UI purposes
|
||||
- enum items can define `label` for UI purposes
|
||||
- most important part is that item can define `children` key where are definitions of it's children (`children` value works the same way as in `dict`)
|
||||
- to set default value for `enum_key` set it with `enum_default`
|
||||
- entity must have defined `"label"` if is not used as widget
|
||||
- is set as group if any parent is not group
|
||||
- if `"label"` is entetered there which will be shown in GUI
|
||||
- item with label can be collapsible
|
||||
- that can be set with key `"collapsible"` as `True`/`False` (Default: `True`)
|
||||
- with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`)
|
||||
- it is possible to add darker background with `"highlight_content"` (Default: `False`)
|
||||
- darker background has limits of maximum applies after 3-4 nested highlighted items there is not difference in the color
|
||||
- output is dictionary `{the "key": children values}`
|
||||
- is set as group if any parent is not group (can't have children as group)
|
||||
- may be with or without `"label"` (only for GUI)
|
||||
- `"label"` must be set to be able mark item as group with `"is_group"` key set to True
|
||||
- item with label can visually wrap it's children
|
||||
- this option is enabled by default to turn off set `"use_label_wrap"` to `False`
|
||||
- label wrap is by default collapsible
|
||||
- that can be set with key `"collapsible"` to `True`/`False`
|
||||
- with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`)
|
||||
- it is possible to add lighter background with `"highlight_content"` (Default: `False`)
|
||||
- lighter background has limits of maximum applies after 3-4 nested highlighted items there is not much difference in the color
|
||||
- for UI porposes was added `enum_is_horizontal` which will make combobox appear next to children inputs instead of on top of them (Default: `False`)
|
||||
- this has extended ability of `enum_on_right` which will move combobox to right side next to children widgets (Default: `False`)
|
||||
- output is dictionary `{the "key": children values}`
|
||||
- using this type as template item for list type can be used to create infinite hierarchies
|
||||
|
||||
```
|
||||
# Example
|
||||
{
|
||||
|
|
@ -298,8 +314,8 @@ How output of the schema could look like on save:
|
|||
```
|
||||
|
||||
## Inputs for setting any kind of value (`Pure` inputs)
|
||||
- all these input must have defined `"key"` under which will be stored and `"label"` which will be shown next to input
|
||||
- unless they are used in different types of inputs (later) "as widgets" in that case `"key"` and `"label"` are not required as there is not place where to set them
|
||||
- all inputs must have defined `"key"` if are not used as dynamic item
|
||||
- they can also have defined `"label"`
|
||||
|
||||
### boolean
|
||||
- simple checkbox, nothing more to set
|
||||
|
|
@ -355,21 +371,15 @@ How output of the schema could look like on save:
|
|||
```
|
||||
|
||||
### path-input
|
||||
- enhanced text input
|
||||
- does not allow to enter backslash, is auto-converted to forward slash
|
||||
- may be added another validations, like do not allow end path with slash
|
||||
- this input is implemented to add additional features to text input
|
||||
- this is meant to be used in proxy input `path-widget`
|
||||
- this is meant to be used in proxy input `path`
|
||||
- DO NOT USE this input in schema please
|
||||
|
||||
### raw-json
|
||||
- a little bit enhanced text input for raw json
|
||||
- can store dictionary (`{}`) or list (`[]`) but not both
|
||||
- by default stores dictionary to change it to list set `is_list` to `True`
|
||||
- has validations of json format
|
||||
- empty value is invalid value, always must be json serializable
|
||||
- valid value types are list `[]` and dictionary `{}`
|
||||
- schema also defines valid value type
|
||||
- by default it is dictionary
|
||||
- to be able use list it is required to define `is_list` to `true`
|
||||
- output can be stored as string
|
||||
- this is to allow any keys in dictionary
|
||||
- set key `store_as_string` to `true`
|
||||
|
|
@ -385,7 +395,7 @@ How output of the schema could look like on save:
|
|||
```
|
||||
|
||||
### enum
|
||||
- returns value of single on multiple items from predefined values
|
||||
- enumeration of values that are predefined in schema
|
||||
- multiselection can be allowed with setting key `"multiselection"` to `True` (Default: `False`)
|
||||
- values are defined under value of key `"enum_items"` as list
|
||||
- each item in list is simple dictionary where value is label and key is value which will be stored
|
||||
|
|
@ -415,6 +425,8 @@ How output of the schema could look like on save:
|
|||
- have only single selection mode
|
||||
- it is possible to define default value `default`
|
||||
- `"work"` is used if default value is not specified
|
||||
- enum values are not updated on the fly it is required to save templates and
|
||||
reset settings to recache values
|
||||
```
|
||||
{
|
||||
"key": "host",
|
||||
|
|
@ -449,6 +461,42 @@ How output of the schema could look like on save:
|
|||
}
|
||||
```
|
||||
|
||||
### apps-enum
|
||||
- enumeration of available application and their variants from system settings
|
||||
- applications without host name are excluded
|
||||
- can be used only in project settings
|
||||
- has only `multiselection`
|
||||
- used only in project anatomy
|
||||
```
|
||||
{
|
||||
"type": "apps-enum",
|
||||
"key": "applications",
|
||||
"label": "Applications"
|
||||
}
|
||||
```
|
||||
|
||||
### tools-enum
|
||||
- enumeration of available tools and their variants from system settings
|
||||
- can be used only in project settings
|
||||
- has only `multiselection`
|
||||
- used only in project anatomy
|
||||
```
|
||||
{
|
||||
"type": "tools-enum",
|
||||
"key": "tools_env",
|
||||
"label": "Tools"
|
||||
}
|
||||
```
|
||||
|
||||
### task-types-enum
|
||||
- enumeration of task types from current project
|
||||
- enum values are not updated on the fly and modifications of task types on project require save and reset to be propagated to this enum
|
||||
- has set `multiselection` to `True` but can be changed to `False` in schema
|
||||
|
||||
### deadline_url-enum
|
||||
- deadline module specific enumerator using deadline system settings to fill it's values
|
||||
- TODO: move this type to deadline module
|
||||
|
||||
## Inputs for setting value using Pure inputs
|
||||
- these inputs also have required `"key"`
|
||||
- attribute `"label"` is required in few conditions
|
||||
|
|
@ -594,7 +642,7 @@ How output of the schema could look like on save:
|
|||
}
|
||||
```
|
||||
|
||||
### path-widget
|
||||
### path
|
||||
- input for paths, use `path-input` internally
|
||||
- has 2 input modifiers `"multiplatform"` and `"multipath"`
|
||||
- `"multiplatform"` - adds `"windows"`, `"linux"` and `"darwin"` path inputs result is dictionary
|
||||
|
|
@ -685,12 +733,13 @@ How output of the schema could look like on save:
|
|||
}
|
||||
```
|
||||
|
||||
### splitter
|
||||
- visual splitter of items (more divider than splitter)
|
||||
### separator
|
||||
- legacy name is `splitter` (still usable)
|
||||
- visual separator of items (more divider than separator)
|
||||
|
||||
```
|
||||
{
|
||||
"type": "splitter"
|
||||
"type": "separator"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,39 @@
|
|||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "ExtractReview",
|
||||
"label": "Extract Review",
|
||||
"children": [
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": false,
|
||||
"key": "jpg_options",
|
||||
"label": "Extracted jpg Options",
|
||||
"children": [
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_representation_tags"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": false,
|
||||
"key": "mov_options",
|
||||
"label": "Extracted mov Options",
|
||||
"children": [
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_representation_tags"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -166,7 +166,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"type": "collapsible-wrap",
|
||||
"label": "Model",
|
||||
|
|
@ -329,6 +328,30 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "collapsible-wrap",
|
||||
"label": "Rig",
|
||||
"children": [
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_publish_plugin",
|
||||
"template_data": [
|
||||
{
|
||||
"key": "ValidateRigContents",
|
||||
"label": "Validate Rig Contents"
|
||||
},
|
||||
{
|
||||
"key": "ValidateRigJointsHidden",
|
||||
"label": "Validate Rig Joints Hidden"
|
||||
},
|
||||
{
|
||||
"key": "ValidateRigControllers",
|
||||
"label": "Validate Rig Controllers"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_publish_plugin",
|
||||
|
|
|
|||
|
|
@ -13,12 +13,8 @@
|
|||
"label": "Image Loader"
|
||||
},
|
||||
{
|
||||
"key": "LoadMov",
|
||||
"label": "Movie Loader"
|
||||
},
|
||||
{
|
||||
"key": "LoadSequence",
|
||||
"label": "Image Sequence Loader"
|
||||
"key": "LoadClip",
|
||||
"label": "Clip Loader"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,13 +13,7 @@
|
|||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "families",
|
||||
"label": "Families",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "representations",
|
||||
"key": "_representations",
|
||||
"label": "Representations",
|
||||
"object_type": "text"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -95,11 +95,11 @@
|
|||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"key": "schema_template_exaples",
|
||||
"key": "template_exaples",
|
||||
"label": "Schema template examples",
|
||||
"children": [
|
||||
{
|
||||
"type": "schema_template",
|
||||
"type": "template",
|
||||
"name": "example_template",
|
||||
"template_data": {
|
||||
"host_label": "Application 1",
|
||||
|
|
@ -108,7 +108,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"type": "template",
|
||||
"name": "example_template",
|
||||
"template_data": {
|
||||
"host_label": "Application 2",
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
"type": "dict",
|
||||
"key": "disk_mapping",
|
||||
"label": "Disk mapping",
|
||||
"is_group": true,
|
||||
"use_label_wrap": false,
|
||||
"collapsible": false,
|
||||
"children": [
|
||||
|
|
|
|||
|
|
@ -38,7 +38,10 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
|
|||
|
||||
# Enable minimize and maximize for app
|
||||
self.setWindowTitle(self.tool_title)
|
||||
self.setWindowFlags(QtCore.Qt.Window)
|
||||
window_flags = QtCore.Qt.Window
|
||||
if not parent:
|
||||
window_flags |= QtCore.Qt.WindowStaysOnTopHint
|
||||
self.setWindowFlags(window_flags)
|
||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
if icon is not None:
|
||||
self.setWindowIcon(icon)
|
||||
|
|
|
|||
|
|
@ -51,7 +51,10 @@ class LoaderWindow(QtWidgets.QDialog):
|
|||
self.family_config_cache = lib.FamilyConfigCache(io)
|
||||
|
||||
# Enable minimize and maximize for app
|
||||
self.setWindowFlags(QtCore.Qt.Window)
|
||||
window_flags = QtCore.Qt.Window
|
||||
if not parent:
|
||||
window_flags |= QtCore.Qt.WindowStaysOnTopHint
|
||||
self.setWindowFlags(window_flags)
|
||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
|
||||
body = QtWidgets.QWidget()
|
||||
|
|
|
|||
|
|
@ -786,7 +786,10 @@ class ThumbnailWidget(QtWidgets.QLabel):
|
|||
|
||||
def scale_pixmap(self, pixmap):
|
||||
return pixmap.scaled(
|
||||
self.width(), self.height(), QtCore.Qt.KeepAspectRatio
|
||||
self.width(),
|
||||
self.height(),
|
||||
QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.SmoothTransformation
|
||||
)
|
||||
|
||||
def set_thumbnail(self, entity=None):
|
||||
|
|
|
|||
|
|
@ -214,7 +214,8 @@ class BaseWidget(QtWidgets.QWidget):
|
|||
def _paste_value_actions(self, menu):
|
||||
output = []
|
||||
# Allow paste of value only if were copied from this UI
|
||||
mime_data = QtWidgets.QApplication.clipboard().mimeData()
|
||||
clipboard = QtWidgets.QApplication.clipboard()
|
||||
mime_data = clipboard.mimeData()
|
||||
mime_value = mime_data.data("application/copy_settings_value")
|
||||
# Skip if there is nothing to do
|
||||
if not mime_value:
|
||||
|
|
|
|||
|
|
@ -508,7 +508,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
first_invalid_item = invalid_items[0]
|
||||
self.scroll_widget.ensureWidgetVisible(first_invalid_item)
|
||||
if first_invalid_item.isVisible():
|
||||
first_invalid_item.setFocus(True)
|
||||
first_invalid_item.setFocus()
|
||||
return False
|
||||
|
||||
def on_saved(self, saved_tab_widget):
|
||||
|
|
|
|||
|
|
@ -128,9 +128,9 @@ class ModifiableDictEmptyItem(QtWidgets.QWidget):
|
|||
def add_new_item(self, key=None, label=None):
|
||||
input_field = self.entity_widget.add_new_key(key, label)
|
||||
if self.collapsible_key:
|
||||
self.key_input.setFocus(True)
|
||||
self.key_input.setFocus()
|
||||
else:
|
||||
input_field.key_input.setFocus(True)
|
||||
input_field.key_input.setFocus()
|
||||
return input_field
|
||||
|
||||
def _on_add_clicked(self):
|
||||
|
|
@ -563,7 +563,7 @@ class ModifiableDictItem(QtWidgets.QWidget):
|
|||
|
||||
def on_add_clicked(self):
|
||||
widget = self.entity_widget.add_new_key(None, None)
|
||||
widget.key_input.setFocus(True)
|
||||
widget.key_input.setFocus()
|
||||
|
||||
def on_edit_pressed(self):
|
||||
if not self.key_input.isVisible():
|
||||
|
|
|
|||
|
|
@ -357,7 +357,7 @@ class ListWidget(InputWidget):
|
|||
new_entity = self.entity.add_new_item(row)
|
||||
input_field = self._input_fields_by_entity_id.get(new_entity.id)
|
||||
if input_field is not None:
|
||||
input_field.input_field.setFocus(True)
|
||||
input_field.input_field.setFocus()
|
||||
return new_entity
|
||||
|
||||
def add_row(self, child_entity, row=None):
|
||||
|
|
|
|||
|
|
@ -268,7 +268,6 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
|
|||
|
||||
# Set modules
|
||||
self.tray_man = TrayManager(self, self.parent)
|
||||
self.tray_man.initialize_modules()
|
||||
|
||||
# Add menu to Context of SystemTrayIcon
|
||||
self.setContextMenu(self.menu)
|
||||
|
|
@ -291,6 +290,17 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
|
|||
self._doubleclick = False
|
||||
self._click_pos = None
|
||||
|
||||
self._initializing_modules = False
|
||||
|
||||
@property
|
||||
def initializing_modules(self):
|
||||
return self._initializing_modules
|
||||
|
||||
def initialize_modules(self):
|
||||
self._initializing_modules = True
|
||||
self.tray_man.initialize_modules()
|
||||
self._initializing_modules = False
|
||||
|
||||
def _click_timer_timeout(self):
|
||||
self._click_timer.stop()
|
||||
doubleclick = self._doubleclick
|
||||
|
|
@ -334,38 +344,48 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
|
|||
QtCore.QCoreApplication.exit()
|
||||
|
||||
|
||||
class TrayMainWindow(QtWidgets.QMainWindow):
|
||||
""" TrayMainWindow is base of Pype application.
|
||||
|
||||
Every widget should have set this window as parent because
|
||||
QSystemTrayIcon widget is not allowed to be a parent of any widget.
|
||||
"""
|
||||
|
||||
class PypeTrayStarter(QtCore.QObject):
|
||||
def __init__(self, app):
|
||||
super(TrayMainWindow, self).__init__()
|
||||
self.app = app
|
||||
app.setQuitOnLastWindowClosed(False)
|
||||
self._app = app
|
||||
self._splash = None
|
||||
|
||||
self.tray_widget = SystemTrayIcon(self)
|
||||
self.tray_widget.show()
|
||||
main_window = QtWidgets.QMainWindow()
|
||||
tray_widget = SystemTrayIcon(main_window)
|
||||
|
||||
start_timer = QtCore.QTimer()
|
||||
start_timer.setInterval(100)
|
||||
start_timer.start()
|
||||
|
||||
class PypeTrayApplication(QtWidgets.QApplication):
|
||||
"""Qt application manages application's control flow."""
|
||||
start_timer.timeout.connect(self._on_start_timer)
|
||||
|
||||
def __init__(self):
|
||||
super(PypeTrayApplication, self).__init__(sys.argv)
|
||||
# Allows to close widgets without exiting app
|
||||
self.setQuitOnLastWindowClosed(False)
|
||||
self._main_window = main_window
|
||||
self._tray_widget = tray_widget
|
||||
self._timer_counter = 0
|
||||
self._start_timer = start_timer
|
||||
|
||||
# Sets up splash
|
||||
splash_widget = self.set_splash()
|
||||
def _on_start_timer(self):
|
||||
if self._timer_counter == 0:
|
||||
self._timer_counter += 1
|
||||
splash = self._get_splash()
|
||||
splash.show()
|
||||
self._tray_widget.show()
|
||||
|
||||
splash_widget.show()
|
||||
self.processEvents()
|
||||
self.main_window = TrayMainWindow(self)
|
||||
splash_widget.hide()
|
||||
elif self._timer_counter == 1:
|
||||
self._timer_counter += 1
|
||||
self._tray_widget.initialize_modules()
|
||||
|
||||
def set_splash(self):
|
||||
elif not self._tray_widget.initializing_modules:
|
||||
splash = self._get_splash()
|
||||
splash.hide()
|
||||
self._start_timer.stop()
|
||||
|
||||
def _get_splash(self):
|
||||
if self._splash is None:
|
||||
self._splash = self._create_splash()
|
||||
return self._splash
|
||||
|
||||
def _create_splash(self):
|
||||
splash_pix = QtGui.QPixmap(resources.get_openpype_splash_filepath())
|
||||
splash = QtWidgets.QSplashScreen(splash_pix)
|
||||
splash.setMask(splash_pix.mask())
|
||||
|
|
@ -377,7 +397,12 @@ class PypeTrayApplication(QtWidgets.QApplication):
|
|||
|
||||
|
||||
def main():
|
||||
app = PypeTrayApplication()
|
||||
app = QtWidgets.QApplication.instance()
|
||||
if not app:
|
||||
app = QtWidgets.QApplication([])
|
||||
|
||||
starter = PypeTrayStarter(app)
|
||||
|
||||
# TODO remove when pype.exe will have an icon
|
||||
if os.name == "nt":
|
||||
import ctypes
|
||||
|
|
|
|||
367
openpype/tools/utils/host_tools.py
Normal file
367
openpype/tools/utils/host_tools.py
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
"""Single access point to all tools usable in hosts.
|
||||
|
||||
It is possible to create `HostToolsHelper` in host implementaion or
|
||||
use singleton approach with global functions (using helper anyway).
|
||||
"""
|
||||
|
||||
import avalon.api
|
||||
|
||||
|
||||
class HostToolsHelper:
|
||||
"""Create and cache tool windows in memory.
|
||||
|
||||
Almost all methods expect parent widget but the parent is used only on
|
||||
first tool creation.
|
||||
|
||||
Class may also contain tools that are available only for one or few hosts.
|
||||
"""
|
||||
def __init__(self, parent=None):
|
||||
self._log = None
|
||||
# Global parent for all tools (may and may not be set)
|
||||
self._parent = parent
|
||||
|
||||
# Prepare attributes for all tools
|
||||
self._workfiles_tool = None
|
||||
self._loader_tool = None
|
||||
self._creator_tool = None
|
||||
self._subset_manager_tool = None
|
||||
self._scene_inventory_tool = None
|
||||
self._library_loader_tool = None
|
||||
self._look_assigner_tool = None
|
||||
|
||||
@property
|
||||
def log(self):
|
||||
if self._log is None:
|
||||
from openpype.api import Logger
|
||||
|
||||
self._log = Logger.get_logger(self.__class__.__name__)
|
||||
return self._log
|
||||
|
||||
def get_workfiles_tool(self, parent):
|
||||
"""Create, cache and return workfiles tool window."""
|
||||
if self._workfiles_tool is None:
|
||||
from openpype.tools.workfiles.app import (
|
||||
Window, validate_host_requirements
|
||||
)
|
||||
# Host validation
|
||||
host = avalon.api.registered_host()
|
||||
validate_host_requirements(host)
|
||||
|
||||
workfiles_window = Window(parent=parent)
|
||||
self._workfiles_tool = workfiles_window
|
||||
|
||||
return self._workfiles_tool
|
||||
|
||||
def show_workfiles(self, parent=None, use_context=None, save=None):
|
||||
"""Workfiles tool for changing context and saving workfiles."""
|
||||
from avalon import style
|
||||
|
||||
if use_context is None:
|
||||
use_context = True
|
||||
|
||||
if save is None:
|
||||
save = True
|
||||
|
||||
workfiles_tool = self.get_workfiles_tool(parent)
|
||||
if use_context:
|
||||
context = {
|
||||
"asset": avalon.api.Session["AVALON_ASSET"],
|
||||
"silo": avalon.api.Session["AVALON_SILO"],
|
||||
"task": avalon.api.Session["AVALON_TASK"]
|
||||
}
|
||||
workfiles_tool.set_context(context)
|
||||
|
||||
if save:
|
||||
workfiles_tool.set_save_enabled(save)
|
||||
|
||||
workfiles_tool.refresh()
|
||||
workfiles_tool.show()
|
||||
# Pull window to the front.
|
||||
workfiles_tool.raise_()
|
||||
workfiles_tool.activateWindow()
|
||||
workfiles_tool.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
def get_loader_tool(self, parent):
|
||||
"""Create, cache and return loader tool window."""
|
||||
if self._loader_tool is None:
|
||||
from openpype.tools.loader import LoaderWindow
|
||||
|
||||
loader_window = LoaderWindow(parent=parent or self._parent)
|
||||
self._loader_tool = loader_window
|
||||
|
||||
return self._loader_tool
|
||||
|
||||
def show_loader(self, parent=None, use_context=None):
|
||||
"""Loader tool for loading representations."""
|
||||
from avalon import style
|
||||
|
||||
loader_tool = self.get_loader_tool(parent)
|
||||
|
||||
loader_tool.show()
|
||||
loader_tool.raise_()
|
||||
loader_tool.activateWindow()
|
||||
|
||||
if use_context is None:
|
||||
use_context = False
|
||||
|
||||
if use_context:
|
||||
context = {"asset": avalon.api.Session["AVALON_ASSET"]}
|
||||
loader_tool.set_context(context, refresh=True)
|
||||
else:
|
||||
loader_tool.refresh()
|
||||
|
||||
loader_tool.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
def get_creator_tool(self, parent):
|
||||
"""Create, cache and return creator tool window."""
|
||||
if self._creator_tool is None:
|
||||
from avalon.tools.creator.app import Window
|
||||
|
||||
creator_window = Window(parent=parent or self._parent)
|
||||
self._creator_tool = creator_window
|
||||
|
||||
return self._creator_tool
|
||||
|
||||
def show_creator(self, parent=None):
|
||||
"""Show tool to create new instantes for publishing."""
|
||||
from avalon import style
|
||||
|
||||
creator_tool = self.get_creator_tool(parent)
|
||||
creator_tool.refresh()
|
||||
creator_tool.show()
|
||||
|
||||
creator_tool.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
# Pull window to the front.
|
||||
creator_tool.raise_()
|
||||
creator_tool.activateWindow()
|
||||
|
||||
def get_subset_manager_tool(self, parent):
|
||||
"""Create, cache and return subset manager tool window."""
|
||||
if self._subset_manager_tool is None:
|
||||
from avalon.tools.subsetmanager import Window
|
||||
|
||||
subset_manager_window = Window(parent=parent or self._parent)
|
||||
self._subset_manager_tool = subset_manager_window
|
||||
|
||||
return self._subset_manager_tool
|
||||
|
||||
def show_subset_manager(self, parent=None):
|
||||
"""Show tool display/remove existing created instances."""
|
||||
from avalon import style
|
||||
|
||||
subset_manager_tool = self.get_subset_manager_tool(parent)
|
||||
subset_manager_tool.show()
|
||||
|
||||
subset_manager_tool.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
# Pull window to the front.
|
||||
subset_manager_tool.raise_()
|
||||
subset_manager_tool.activateWindow()
|
||||
|
||||
def get_scene_inventory_tool(self, parent):
|
||||
"""Create, cache and return scene inventory tool window."""
|
||||
if self._scene_inventory_tool is None:
|
||||
from avalon.tools.sceneinventory.app import Window
|
||||
|
||||
scene_inventory_window = Window(parent=parent or self._parent)
|
||||
self._scene_inventory_tool = scene_inventory_window
|
||||
|
||||
return self._scene_inventory_tool
|
||||
|
||||
def show_scene_inventory(self, parent=None):
|
||||
"""Show tool maintain loaded containers."""
|
||||
from avalon import style
|
||||
|
||||
scene_inventory_tool = self.get_scene_inventory_tool(parent)
|
||||
scene_inventory_tool.show()
|
||||
scene_inventory_tool.refresh()
|
||||
scene_inventory_tool.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
# Pull window to the front.
|
||||
scene_inventory_tool.raise_()
|
||||
scene_inventory_tool.activateWindow()
|
||||
|
||||
def get_library_loader_tool(self, parent):
|
||||
"""Create, cache and return library loader tool window."""
|
||||
if self._library_loader_tool is None:
|
||||
from openpype.tools.libraryloader import LibraryLoaderWindow
|
||||
|
||||
library_window = LibraryLoaderWindow(
|
||||
parent=parent or self._parent
|
||||
)
|
||||
self._library_loader_tool = library_window
|
||||
|
||||
return self._library_loader_tool
|
||||
|
||||
def show_library_loader(self, parent=None):
|
||||
"""Loader tool for loading representations from library project."""
|
||||
from avalon import style
|
||||
|
||||
library_loader_tool = self.get_library_loader_tool(parent)
|
||||
library_loader_tool.show()
|
||||
library_loader_tool.raise_()
|
||||
library_loader_tool.activateWindow()
|
||||
library_loader_tool.refresh()
|
||||
library_loader_tool.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
def show_publish(self, parent=None):
|
||||
"""Publish UI."""
|
||||
from avalon.tools import publish
|
||||
|
||||
publish.show(parent)
|
||||
|
||||
def get_look_assigner_tool(self, parent):
|
||||
"""Create, cache and return look assigner tool window."""
|
||||
if self._look_assigner_tool is None:
|
||||
import mayalookassigner
|
||||
|
||||
mayalookassigner_window = mayalookassigner.App(parent)
|
||||
self._look_assigner_tool = mayalookassigner_window
|
||||
return self._look_assigner_tool
|
||||
|
||||
def show_look_assigner(self, parent=None):
|
||||
"""Look manager is Maya specific tool for look management."""
|
||||
from avalon import style
|
||||
|
||||
look_assigner_tool = self.get_look_assigner_tool(parent)
|
||||
look_assigner_tool.show()
|
||||
look_assigner_tool.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
def get_tool_by_name(self, tool_name, parent=None, *args, **kwargs):
|
||||
"""Show tool by it's name.
|
||||
|
||||
This is helper for
|
||||
"""
|
||||
if tool_name == "workfiles":
|
||||
return self.get_workfiles_tool(parent, *args, **kwargs)
|
||||
|
||||
elif tool_name == "loader":
|
||||
return self.get_loader_tool(parent, *args, **kwargs)
|
||||
|
||||
elif tool_name == "libraryloader":
|
||||
return self.get_library_loader_tool(parent, *args, **kwargs)
|
||||
|
||||
elif tool_name == "creator":
|
||||
return self.get_creator_tool(parent, *args, **kwargs)
|
||||
|
||||
elif tool_name == "subsetmanager":
|
||||
return self.get_subset_manager_tool(parent, *args, **kwargs)
|
||||
|
||||
elif tool_name == "sceneinventory":
|
||||
return self.get_scene_inventory_tool(parent, *args, **kwargs)
|
||||
|
||||
elif tool_name == "lookassigner":
|
||||
return self.get_look_assigner_tool(parent, *args, **kwargs)
|
||||
|
||||
elif tool_name == "publish":
|
||||
self.log.info("Can't return publish tool window.")
|
||||
|
||||
else:
|
||||
self.log.warning(
|
||||
"Can't show unknown tool name: \"{}\"".format(tool_name)
|
||||
)
|
||||
|
||||
def show_tool_by_name(self, tool_name, parent=None, *args, **kwargs):
|
||||
"""Show tool by it's name.
|
||||
|
||||
This is helper for
|
||||
"""
|
||||
if tool_name == "workfiles":
|
||||
self.show_workfiles(parent, *args, **kwargs)
|
||||
|
||||
elif tool_name == "loader":
|
||||
self.show_loader(parent, *args, **kwargs)
|
||||
|
||||
elif tool_name == "libraryloader":
|
||||
self.show_library_loader(parent, *args, **kwargs)
|
||||
|
||||
elif tool_name == "creator":
|
||||
self.show_creator(parent, *args, **kwargs)
|
||||
|
||||
elif tool_name == "subsetmanager":
|
||||
self.show_subset_manager(parent, *args, **kwargs)
|
||||
|
||||
elif tool_name == "sceneinventory":
|
||||
self.show_scene_inventory(parent, *args, **kwargs)
|
||||
|
||||
elif tool_name == "lookassigner":
|
||||
self.show_look_assigner(parent, *args, **kwargs)
|
||||
|
||||
elif tool_name == "publish":
|
||||
self.show_publish(parent, *args, **kwargs)
|
||||
|
||||
else:
|
||||
self.log.warning(
|
||||
"Can't show unknown tool name: \"{}\"".format(tool_name)
|
||||
)
|
||||
|
||||
|
||||
class _SingletonPoint:
|
||||
"""Singleton access to host tools.
|
||||
|
||||
Some hosts don't have ability to create 'HostToolsHelper' object anc can
|
||||
only register function callbacks. For those cases is created this singleton
|
||||
point where 'HostToolsHelper' is created "in shared memory".
|
||||
"""
|
||||
helper = None
|
||||
|
||||
@classmethod
|
||||
def _create_helper(cls):
|
||||
if cls.helper is None:
|
||||
cls.helper = HostToolsHelper()
|
||||
|
||||
@classmethod
|
||||
def show_tool_by_name(cls, tool_name, parent=None, *args, **kwargs):
|
||||
cls._create_helper()
|
||||
cls.helper.show_tool_by_name(tool_name, parent, *args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def get_tool_by_name(cls, tool_name, parent=None, *args, **kwargs):
|
||||
cls._create_helper()
|
||||
return cls.helper.get_tool_by_name(tool_name, parent, *args, **kwargs)
|
||||
|
||||
|
||||
# Function callbacks using singleton acces point
|
||||
def get_tool_by_name(tool_name, parent=None, *args, **kwargs):
|
||||
return _SingletonPoint.get_tool_by_name(tool_name, parent, *args, **kwargs)
|
||||
|
||||
|
||||
def show_tool_by_name(tool_name, parent=None, *args, **kwargs):
|
||||
_SingletonPoint.show_tool_by_name(tool_name, parent, *args, **kwargs)
|
||||
|
||||
|
||||
def show_workfiles(parent=None, use_context=None, save=None):
|
||||
_SingletonPoint.show_tool_by_name(
|
||||
"workfiles", parent, use_context=use_context, save=save
|
||||
)
|
||||
|
||||
|
||||
def show_loader(parent=None, use_context=None):
|
||||
_SingletonPoint.show_tool_by_name(
|
||||
"loader", parent, use_context=use_context
|
||||
)
|
||||
|
||||
|
||||
def show_library_loader(parent=None):
|
||||
_SingletonPoint.show_tool_by_name("libraryloader", parent)
|
||||
|
||||
|
||||
def show_creator(parent=None):
|
||||
_SingletonPoint.show_tool_by_name("creator", parent)
|
||||
|
||||
|
||||
def show_subset_manager(parent=None):
|
||||
_SingletonPoint.show_tool_by_name("subsetmanager", parent)
|
||||
|
||||
|
||||
def show_scene_inventory(parent=None):
|
||||
_SingletonPoint.show_tool_by_name("sceneinventory", parent)
|
||||
|
||||
|
||||
def show_look_assigner(parent=None):
|
||||
_SingletonPoint.show_tool_by_name("lookassigner", parent)
|
||||
|
||||
|
||||
def show_publish(parent=None):
|
||||
_SingletonPoint.show_tool_by_name("publish", parent)
|
||||
|
|
@ -944,7 +944,10 @@ class Window(QtWidgets.QMainWindow):
|
|||
def __init__(self, parent=None):
|
||||
super(Window, self).__init__(parent=parent)
|
||||
self.setWindowTitle(self.title)
|
||||
self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.WindowCloseButtonHint)
|
||||
window_flags = QtCore.Qt.Window | QtCore.Qt.WindowCloseButtonHint
|
||||
if not parent:
|
||||
window_flags |= QtCore.Qt.WindowStaysOnTopHint
|
||||
self.setWindowFlags(window_flags)
|
||||
|
||||
# Create pages widget and set it as central widget
|
||||
pages_widget = QtWidgets.QStackedWidget(self)
|
||||
|
|
@ -1015,6 +1018,9 @@ class Window(QtWidgets.QMainWindow):
|
|||
|
||||
"""
|
||||
|
||||
def set_save_enabled(self, enabled):
|
||||
self.files_widget.btn_save.setEnabled(enabled)
|
||||
|
||||
def on_task_changed(self):
|
||||
# Since we query the disk give it slightly more delay
|
||||
tools_lib.schedule(self._on_task_changed, 100, channel="mongo")
|
||||
|
|
@ -1187,7 +1193,7 @@ def show(root=None, debug=False, parent=None, use_context=True, save=True):
|
|||
}
|
||||
window.set_context(context)
|
||||
|
||||
window.files_widget.btn_save.setEnabled(save)
|
||||
window.set_save_enabled(save)
|
||||
|
||||
window.show()
|
||||
window.setStyleSheet(style.load_stylesheet())
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring Pype version."""
|
||||
__version__ = "3.5.0-nightly.5"
|
||||
__version__ = "3.5.0"
|
||||
|
|
|
|||
83
poetry.lock
generated
83
poetry.lock
generated
|
|
@ -288,15 +288,20 @@ test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pret
|
|||
|
||||
[[package]]
|
||||
name = "cx-freeze"
|
||||
version = "6.6"
|
||||
version = "6.7"
|
||||
description = "Create standalone executables from Python scripts"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
cx-Logging = {version = ">=3.0", markers = "sys_platform == \"win32\""}
|
||||
importlib-metadata = ">=3.1.1"
|
||||
cx-logging = {version = ">=3.0", markers = "sys_platform == \"win32\""}
|
||||
importlib-metadata = ">=4.3.1"
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://distribute.openpype.io/wheels"
|
||||
reference = "openpype"
|
||||
|
||||
[[package]]
|
||||
name = "cx-logging"
|
||||
|
|
@ -1013,34 +1018,6 @@ category = "main"
|
|||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "pyqt5"
|
||||
version = "5.15.4"
|
||||
description = "Python bindings for the Qt cross platform application toolkit"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
PyQt5-Qt5 = ">=5.15"
|
||||
PyQt5-sip = ">=12.8,<13"
|
||||
|
||||
[[package]]
|
||||
name = "pyqt5-qt5"
|
||||
version = "5.15.2"
|
||||
description = "The subset of a Qt installation needed by PyQt5."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pyqt5-sip"
|
||||
version = "12.9.0"
|
||||
description = "The sip module support for PyQt5"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "pyrsistent"
|
||||
version = "0.17.3"
|
||||
|
|
@ -1814,17 +1791,7 @@ cryptography = [
|
|||
{file = "cryptography-3.4.7-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:bf40af59ca2465b24e54f671b2de2c59257ddc4f7e5706dbd6930e26823668d3"},
|
||||
{file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"},
|
||||
]
|
||||
cx-freeze = [
|
||||
{file = "cx_Freeze-6.6-cp36-cp36m-win32.whl", hash = "sha256:b3d3a6bcd1a07c50b4e1c907f14842642156110e63a99cd5c73b8a24751e9b97"},
|
||||
{file = "cx_Freeze-6.6-cp36-cp36m-win_amd64.whl", hash = "sha256:1935266ec644ea4f7e584985f44cefc0622a449a09980d990833a1a2afcadac8"},
|
||||
{file = "cx_Freeze-6.6-cp37-cp37m-win32.whl", hash = "sha256:1eac2b0f254319cc641ce25bd83337effd7936092562fde701f3ffb40e0274ec"},
|
||||
{file = "cx_Freeze-6.6-cp37-cp37m-win_amd64.whl", hash = "sha256:2bc46ef6d510811b6002f34a3ae4cbfdea44e18644febd2a404d3ee8e48a9fc4"},
|
||||
{file = "cx_Freeze-6.6-cp38-cp38-win32.whl", hash = "sha256:46eb50ebc46f7ae236d16c6a52671ab0f7bb479bea668da19f4b6de3cc413e9e"},
|
||||
{file = "cx_Freeze-6.6-cp38-cp38-win_amd64.whl", hash = "sha256:8c3b00476ce385bb58595bffce55aed031e5a6e16ab6e14d8bee9d1d569e46c3"},
|
||||
{file = "cx_Freeze-6.6-cp39-cp39-win32.whl", hash = "sha256:6e9340cbcf52d4836980ecc83ddba4f7704ff6654dd41168c146b74f512977ce"},
|
||||
{file = "cx_Freeze-6.6-cp39-cp39-win_amd64.whl", hash = "sha256:2fcf1c8b77ae5c06f45be3a9aff79e1dd808c0d624e97561f840dec5ea9b214a"},
|
||||
{file = "cx_Freeze-6.6.tar.gz", hash = "sha256:c4af8ad3f7e7d71e291c1dec5d0fb26bbe92df834b098ed35434c901fbd6762f"},
|
||||
]
|
||||
cx-freeze = []
|
||||
cx-logging = [
|
||||
{file = "cx_Logging-3.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9fcd297e5c51470521c47eff0f86ba844aeca6be97e13c3e2114ebdf03fa3c96"},
|
||||
{file = "cx_Logging-3.0-cp36-cp36m-win32.whl", hash = "sha256:0df4be47c5022cc54316949e283403214568ef599817ced0c0972183d6d4fabb"},
|
||||
|
|
@ -2355,38 +2322,6 @@ pyparsing = [
|
|||
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
||||
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
|
||||
]
|
||||
pyqt5 = [
|
||||
{file = "PyQt5-5.15.4-cp36.cp37.cp38.cp39-abi3-macosx_10_13_intel.whl", hash = "sha256:8c0848ba790a895801d5bfd171da31cad3e551dbcc4e59677a3b622de2ceca98"},
|
||||
{file = "PyQt5-5.15.4-cp36.cp37.cp38.cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:883a549382fc22d29a0568f3ef20b38c8e7ab633a59498ac4eb63a3bf36d3fd3"},
|
||||
{file = "PyQt5-5.15.4-cp36.cp37.cp38.cp39-none-win32.whl", hash = "sha256:a88526a271e846e44779bb9ad7a738c6d3c4a9d01e15a128ecfc6dd4696393b7"},
|
||||
{file = "PyQt5-5.15.4-cp36.cp37.cp38.cp39-none-win_amd64.whl", hash = "sha256:213bebd51821ed89b4d5b35bb10dbe67564228b3568f463a351a08e8b1677025"},
|
||||
{file = "PyQt5-5.15.4.tar.gz", hash = "sha256:2a69597e0dd11caabe75fae133feca66387819fc9bc050f547e5551bce97e5be"},
|
||||
]
|
||||
pyqt5-qt5 = [
|
||||
{file = "PyQt5_Qt5-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154"},
|
||||
{file = "PyQt5_Qt5-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a"},
|
||||
{file = "PyQt5_Qt5-5.15.2-py3-none-win32.whl", hash = "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327"},
|
||||
{file = "PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962"},
|
||||
]
|
||||
pyqt5-sip = [
|
||||
{file = "PyQt5_sip-12.9.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:d85002238b5180bce4b245c13d6face848faa1a7a9e5c6e292025004f2fd619a"},
|
||||
{file = "PyQt5_sip-12.9.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:83c3220b1ca36eb8623ba2eb3766637b19eb0ce9f42336ad8253656d32750c0a"},
|
||||
{file = "PyQt5_sip-12.9.0-cp36-cp36m-win32.whl", hash = "sha256:d8b2bdff7bbf45bc975c113a03b14fd669dc0c73e1327f02706666a7dd51a197"},
|
||||
{file = "PyQt5_sip-12.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:69a3ad4259172e2b1aa9060de211efac39ddd734a517b1924d9c6c0cc4f55f96"},
|
||||
{file = "PyQt5_sip-12.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:42274a501ab4806d2c31659170db14c282b8313d2255458064666d9e70d96206"},
|
||||
{file = "PyQt5_sip-12.9.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6a8701892a01a5a2a4720872361197cc80fdd5f49c8482d488ddf38c9c84f055"},
|
||||
{file = "PyQt5_sip-12.9.0-cp37-cp37m-win32.whl", hash = "sha256:ac57d796c78117eb39edd1d1d1aea90354651efac9d3590aac67fa4983f99f1f"},
|
||||
{file = "PyQt5_sip-12.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4347bd81d30c8e3181e553b3734f91658cfbdd8f1a19f254777f906870974e6d"},
|
||||
{file = "PyQt5_sip-12.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c446971c360a0a1030282a69375a08c78e8a61d568bfd6dab3dcc5cf8817f644"},
|
||||
{file = "PyQt5_sip-12.9.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fc43f2d7c438517ee33e929e8ae77132749c15909afab6aeece5fcf4147ffdb5"},
|
||||
{file = "PyQt5_sip-12.9.0-cp38-cp38-win32.whl", hash = "sha256:055581c6fed44ba4302b70eeb82e979ff70400037358908f251cd85cbb3dbd93"},
|
||||
{file = "PyQt5_sip-12.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:c5216403d4d8d857ec4a61f631d3945e44fa248aa2415e9ee9369ab7c8a4d0c7"},
|
||||
{file = "PyQt5_sip-12.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a25b9843c7da6a1608f310879c38e6434331aab1dc2fe6cb65c14f1ecf33780e"},
|
||||
{file = "PyQt5_sip-12.9.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:dd05c768c2b55ffe56a9d49ce6cc77cdf3d53dbfad935258a9e347cbfd9a5850"},
|
||||
{file = "PyQt5_sip-12.9.0-cp39-cp39-win32.whl", hash = "sha256:4f8e05fe01d54275877c59018d8e82dcdd0bc5696053a8b830eecea3ce806121"},
|
||||
{file = "PyQt5_sip-12.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:b09f4cd36a4831229fb77c424d89635fa937d97765ec90685e2f257e56a2685a"},
|
||||
{file = "PyQt5_sip-12.9.0.tar.gz", hash = "sha256:d3e4489d7c2b0ece9d203ae66e573939f7f60d4d29e089c9f11daa17cfeaae32"},
|
||||
]
|
||||
pyrsistent = [
|
||||
{file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ Pillow = "^8.1" # only used for slates prototype
|
|||
pyblish-base = "^1.8.8"
|
||||
pynput = "^1.7.2" # idle manager in tray
|
||||
pymongo = "^3.11.2"
|
||||
pyqt5 = "^5.12.2" # ideally should be replaced with PySide2
|
||||
"Qt.py" = "^1.3.3"
|
||||
speedcopy = "^2.1"
|
||||
six = "^1.15"
|
||||
|
|
@ -63,14 +62,16 @@ jinxed = [
|
|||
python3-xlib = { version="*", markers = "sys_platform == 'linux'"}
|
||||
enlighten = "^1.9.0"
|
||||
slack-sdk = "^3.6.0"
|
||||
requests = "2.25.1"
|
||||
pysftp = "^0.2.9"
|
||||
dropbox = "^11.20.0"
|
||||
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
flake8 = "^3.7"
|
||||
autopep8 = "^1.4"
|
||||
coverage = "*"
|
||||
cx_freeze = "^6.6"
|
||||
cx_freeze = { version = "6.7", source = "openpype" }
|
||||
GitPython = "^3.1.17"
|
||||
jedi = "^0.13"
|
||||
Jinja2 = "^2.11"
|
||||
|
|
@ -89,7 +90,6 @@ wheel = "*"
|
|||
enlighten = "*" # cool terminal progress bars
|
||||
toml = "^0.10.2" # for parsing pyproject.toml
|
||||
|
||||
|
||||
[tool.poetry.urls]
|
||||
"Bug Tracker" = "https://github.com/pypeclub/openpype/issues"
|
||||
"Discussions" = "https://github.com/pypeclub/openpype/discussions"
|
||||
|
|
@ -104,6 +104,12 @@ build-backend = "poetry.core.masonry.api"
|
|||
|
||||
[openpype]
|
||||
|
||||
[openpype.pyside2]
|
||||
# note: in here we can use pip version specifiers as this is installed with pip until
|
||||
# Poetry will support custom location (-t flag for pip)
|
||||
# https://pip.pypa.io/en/stable/cli/pip_install/#requirement-specifiers
|
||||
version = "==5.15.2"
|
||||
|
||||
[openpype.thirdparty.ffmpeg.windows]
|
||||
url = "https://distribute.openpype.io/thirdparty/ffmpeg-4.4-windows.zip"
|
||||
hash = "dd51ba29d64ee238e7c4c3c7301b19754c3f0ee2e2a729c20a0e2789e72db925"
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 4b80f81e66aca593784be8b299110a0b6541276f
|
||||
Subproject commit 7e5efd6885330d84bb8495975bcab84df49bfa3d
|
||||
5
setup.py
5
setup.py
|
|
@ -59,13 +59,14 @@ includes = []
|
|||
excludes = [
|
||||
"openpype"
|
||||
]
|
||||
bin_includes = []
|
||||
bin_includes = [
|
||||
"vendor"
|
||||
]
|
||||
include_files = [
|
||||
"igniter",
|
||||
"openpype",
|
||||
"repos",
|
||||
"schema",
|
||||
"vendor",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
]
|
||||
|
|
|
|||
9
start.py
9
start.py
|
|
@ -124,6 +124,10 @@ else:
|
|||
paths.append(frozen_libs)
|
||||
os.environ["PYTHONPATH"] = os.pathsep.join(paths)
|
||||
|
||||
# Vendored python modules that must not be in PYTHONPATH environment but
|
||||
# are required for OpenPype processes
|
||||
vendor_python_path = os.path.join(OPENPYPE_ROOT, "vendor", "python")
|
||||
sys.path.insert(0, vendor_python_path)
|
||||
|
||||
import blessed # noqa: E402
|
||||
import certifi # noqa: E402
|
||||
|
|
@ -287,9 +291,8 @@ def run_disk_mapping_commands(mongo_url):
|
|||
if not disk_mapping:
|
||||
return
|
||||
|
||||
for mapping in disk_mapping.get(low_platform):
|
||||
source, destination = mapping
|
||||
|
||||
mappings = disk_mapping.get(low_platform) or []
|
||||
for source, destination in mappings:
|
||||
args = ["subst", destination.rstrip('/'), source.rstrip('/')]
|
||||
_print("disk mapping args:: {}".format(args))
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ BICyan='\033[1;96m' # Cyan
|
|||
BIWhite='\033[1;97m' # White
|
||||
|
||||
args=$@
|
||||
disable_submodule_update = 0
|
||||
disable_submodule_update=0
|
||||
while :; do
|
||||
case $1 in
|
||||
--no-submodule-update)
|
||||
|
|
@ -90,6 +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; }
|
||||
local version_command
|
||||
version_command="import sys;print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))"
|
||||
local python_version
|
||||
|
|
@ -122,7 +123,7 @@ clean_pyc () {
|
|||
local path
|
||||
path=$openpype_root
|
||||
echo -e "${BIGreen}>>>${RST} Cleaning pyc at [ ${BIWhite}$path${RST} ] ... \c"
|
||||
find "$path" -path ./build -prune -o -regex '^.*\(__pycache__\|\.py[co]\)$' -delete
|
||||
find "$path" -path ./build -o -regex '^.*\(__pycache__\|\.py[co]\)$' -delete
|
||||
|
||||
echo -e "${BIGreen}DONE${RST}"
|
||||
}
|
||||
|
|
@ -173,7 +174,7 @@ main () {
|
|||
else
|
||||
echo -e "${BIYellow}NOT FOUND${RST}"
|
||||
echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..."
|
||||
. "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; }
|
||||
. "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return 1; }
|
||||
fi
|
||||
|
||||
if [ "$disable_submodule_update" == 1 ]; then
|
||||
|
|
@ -184,9 +185,9 @@ if [ "$disable_submodule_update" == 1 ]; then
|
|||
fi
|
||||
echo -e "${BIGreen}>>>${RST} Building ..."
|
||||
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||
"$POETRY_HOME/bin/poetry" run python "$openpype_root/setup.py" build > "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return; }
|
||||
"$POETRY_HOME/bin/poetry" run python "$openpype_root/setup.py" build &> "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return 1; }
|
||||
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} Build failed, see the build log."; return; }
|
||||
"$POETRY_HOME/bin/poetry" run python "$openpype_root/setup.py" bdist_mac &> "$openpype_root/build/build.log" || { 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"
|
||||
|
||||
|
|
@ -210,4 +211,6 @@ if [ "$disable_submodule_update" == 1 ]; then
|
|||
echo -e "${BIWhite}$openpype_root/build${RST} directory."
|
||||
}
|
||||
|
||||
main
|
||||
return_code=0
|
||||
main || return_code=$?
|
||||
exit $return_code
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import sys
|
|||
import site
|
||||
from distutils.util import get_platform
|
||||
import platform
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import blessed
|
||||
|
|
@ -93,18 +94,18 @@ _print("Getting venv site-packages ...")
|
|||
assert site_pkg, "No venv site-packages are found."
|
||||
_print(f"Working with: {site_pkg}", 2)
|
||||
|
||||
|
||||
build_dir = "exe.{}-{}".format(get_platform(), sys.version[0:3])
|
||||
openpype_root = Path(os.path.dirname(__file__)).parent
|
||||
|
||||
# create full path
|
||||
if platform.system().lower() == "darwin":
|
||||
build_dir = Path(os.path.dirname(__file__)).parent.joinpath(
|
||||
build_dir = openpype_root.joinpath(
|
||||
"build",
|
||||
"OpenPype.app",
|
||||
"Contents",
|
||||
"MacOS")
|
||||
else:
|
||||
build_dir = Path(os.path.dirname(__file__)).parent / "build" / build_dir
|
||||
build_subdir = "exe.{}-{}".format(get_platform(), sys.version[0:3])
|
||||
build_dir = openpype_root / "build" / build_subdir
|
||||
|
||||
_print(f"Using build at {build_dir}", 2)
|
||||
if not build_dir.exists():
|
||||
|
|
@ -112,7 +113,28 @@ if not build_dir.exists():
|
|||
_print("Probably freezing of code failed. Check ./build/build.log", 3)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _progress(_base, _names):
|
||||
progress_bar.update()
|
||||
return []
|
||||
|
||||
|
||||
deps_dir = build_dir / "dependencies"
|
||||
vendor_dir = build_dir / "vendor"
|
||||
vendor_src = openpype_root / "vendor"
|
||||
|
||||
# copy vendor files
|
||||
_print("Copying vendor files ...")
|
||||
|
||||
total_files = count_folders(vendor_src)
|
||||
progress_bar = enlighten.Counter(
|
||||
total=total_files, desc="Copying vendor files ...",
|
||||
units="%", color=(64, 128, 222))
|
||||
|
||||
shutil.copytree(vendor_src.as_posix(),
|
||||
vendor_dir.as_posix(),
|
||||
ignore=_progress)
|
||||
progress_bar.close()
|
||||
|
||||
# copy all files
|
||||
_print("Copying dependencies ...")
|
||||
|
|
@ -122,12 +144,6 @@ progress_bar = enlighten.Counter(
|
|||
total=total_files, desc="Processing Dependencies",
|
||||
units="%", color=(53, 178, 202))
|
||||
|
||||
|
||||
def _progress(_base, _names):
|
||||
progress_bar.update()
|
||||
return []
|
||||
|
||||
|
||||
shutil.copytree(site_pkg.as_posix(),
|
||||
deps_dir.as_posix(),
|
||||
ignore=_progress)
|
||||
|
|
@ -135,15 +151,29 @@ progress_bar.close()
|
|||
# iterate over frozen libs and create list to delete
|
||||
libs_dir = build_dir / "lib"
|
||||
|
||||
# On Windows "python3.dll" is needed for PyQt5 from the build.
|
||||
if platform.system().lower() == "windows":
|
||||
src = Path(libs_dir / "PyQt5" / "python3.dll")
|
||||
dst = Path(deps_dir / "PyQt5" / "python3.dll")
|
||||
if src.exists():
|
||||
shutil.copyfile(src, dst)
|
||||
else:
|
||||
_print("Could not find {}".format(src), 1)
|
||||
sys.exit(1)
|
||||
|
||||
# On Linux use rpath from source libraries in destination libraries
|
||||
if platform.system().lower() == "linux":
|
||||
src_pyside_dir = openpype_root / "vendor" / "python" / "PySide2"
|
||||
dst_pyside_dir = build_dir / "vendor" / "python" / "PySide2"
|
||||
src_rpath_per_so_file = {}
|
||||
for filepath in src_pyside_dir.glob("*.so"):
|
||||
filename = filepath.name
|
||||
rpath = (
|
||||
subprocess.check_output(["patchelf", "--print-rpath", filepath])
|
||||
.decode("utf-8")
|
||||
.strip()
|
||||
)
|
||||
src_rpath_per_so_file[filename] = rpath
|
||||
|
||||
for filepath in dst_pyside_dir.glob("*.so"):
|
||||
filename = filepath.name
|
||||
if filename not in src_rpath_per_so_file:
|
||||
continue
|
||||
src_rpath = src_rpath_per_so_file[filename]
|
||||
subprocess.check_call(
|
||||
["patchelf", "--set-rpath", src_rpath, filepath]
|
||||
)
|
||||
|
||||
to_delete = []
|
||||
# _print("Finding duplicates ...")
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ def get_release_type_github(Log, github_token):
|
|||
return "minor"
|
||||
|
||||
if any(label in labels for label in patch_labels):
|
||||
return "path"
|
||||
return "patch"
|
||||
|
||||
return None
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,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; }
|
||||
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"
|
||||
|
|
@ -125,7 +126,7 @@ clean_pyc () {
|
|||
local path
|
||||
path=$openpype_root
|
||||
echo -e "${BIGreen}>>>${RST} Cleaning pyc at [ ${BIWhite}$path${RST} ] ... \c"
|
||||
find "$path" -path ./build -prune -o -regex '^.*\(__pycache__\|\.py[co]\)$' -delete
|
||||
find "$path" -path ./build -o -regex '^.*\(__pycache__\|\.py[co]\)$' -delete
|
||||
echo -e "${BIGreen}DONE${RST}"
|
||||
}
|
||||
|
||||
|
|
@ -166,7 +167,7 @@ main () {
|
|||
echo -e "${BIGreen}OK${RST}"
|
||||
else
|
||||
echo -e "${BIYellow}NOT FOUND${RST}"
|
||||
install_poetry || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; }
|
||||
install_poetry || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return 1; }
|
||||
fi
|
||||
|
||||
if [ -f "$openpype_root/poetry.lock" ]; then
|
||||
|
|
@ -175,7 +176,11 @@ main () {
|
|||
echo -e "${BIGreen}>>>${RST} Installing dependencies ..."
|
||||
fi
|
||||
|
||||
"$POETRY_HOME/bin/poetry" install --no-root $poetry_verbosity || { echo -e "${BIRed}!!!${RST} Poetry environment installation failed"; return; }
|
||||
"$POETRY_HOME/bin/poetry" install --no-root $poetry_verbosity || { echo -e "${BIRed}!!!${RST} Poetry environment installation failed"; return 1; }
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo -e "${BIRed}!!!${RST} Virtual environment creation failed."
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Cleaning cache files ..."
|
||||
clean_pyc
|
||||
|
|
@ -183,11 +188,15 @@ main () {
|
|||
# reinstall these because of bug in poetry? or cx_freeze?
|
||||
# cx_freeze will crash on missing __pychache__ on these but
|
||||
# reinstalling them solves the problem.
|
||||
echo -e "${BIGreen}>>>${RST} Fixing pycache bug ..."
|
||||
"$POETRY_HOME/bin/poetry" run python -m pip install --force-reinstall pip
|
||||
"$POETRY_HOME/bin/poetry" run pip install --force-reinstall setuptools
|
||||
"$POETRY_HOME/bin/poetry" run pip install --force-reinstall wheel
|
||||
"$POETRY_HOME/bin/poetry" run python -m pip install --force-reinstall pip
|
||||
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
|
||||
"$POETRY_HOME/bin/poetry" run pip install --disable-pip-version-check --force-reinstall cx_freeze -i $openpype_index --extra-index-url https://pypi.org/simple
|
||||
}
|
||||
|
||||
main -3
|
||||
return_code=0
|
||||
main || return_code=$?
|
||||
exit $return_code
|
||||
|
|
|
|||
|
|
@ -20,6 +20,41 @@ realpath () {
|
|||
echo $(cd $(dirname "$1"); pwd)/$(basename "$1")
|
||||
}
|
||||
|
||||
create_container () {
|
||||
if [ ! -f "$openpype_root/build/docker-image.id" ]; then
|
||||
echo -e "${BIRed}!!!${RST} Docker command failed, cannot find image id."
|
||||
exit 1
|
||||
fi
|
||||
local id=$(<"$openpype_root/build/docker-image.id")
|
||||
echo -e "${BIYellow}---${RST} Creating container from $id ..."
|
||||
cid="$(docker create $id bash)"
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo -e "${BIRed}!!!${RST} Cannot create container."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
retrieve_build_log () {
|
||||
create_container
|
||||
echo -e "${BIYellow}***${RST} Copying build log to ${BIWhite}$openpype_root/build/build.log${RST}"
|
||||
docker cp "$cid:/opt/openpype/build/build.log" "$openpype_root/build"
|
||||
}
|
||||
|
||||
openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}")))
|
||||
|
||||
|
||||
if [ -z $1 ]; then
|
||||
dockerfile="Dockerfile"
|
||||
else
|
||||
dockerfile="Dockerfile.$1"
|
||||
if [ ! -f "$openpype_root/$dockerfile" ]; then
|
||||
echo -e "${BIRed}!!!${RST} Dockerfile for specifed platform ${BIWhite}$1${RST} doesn't exist."
|
||||
exit 1
|
||||
else
|
||||
echo -e "${BIGreen}>>>${RST} Using Dockerfile for ${BIWhite}$1${RST} ..."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Main
|
||||
main () {
|
||||
openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}")))
|
||||
|
|
@ -28,36 +63,35 @@ main () {
|
|||
echo -e "${BIYellow}---${RST} Cleaning build directory ..."
|
||||
rm -rf "$openpype_root/build" && mkdir "$openpype_root/build" > /dev/null
|
||||
|
||||
version_command="import os;exec(open(os.path.join('$openpype_root', 'openpype', 'version.py')).read());print(__version__);"
|
||||
openpype_version="$(python3 <<< ${version_command})"
|
||||
local version_command="import os;exec(open(os.path.join('$openpype_root', 'openpype', 'version.py')).read());print(__version__);"
|
||||
local openpype_version="$(python3 <<< ${version_command})"
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Running docker build ..."
|
||||
docker build --pull --no-cache -t pypeclub/openpype:$openpype_version .
|
||||
# docker build --pull --no-cache -t pypeclub/openpype:$openpype_version .
|
||||
docker build --pull --iidfile $openpype_root/build/docker-image.id -t pypeclub/openpype:$openpype_version -f $dockerfile .
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo $?
|
||||
echo -e "${BIRed}!!!${RST} Docker build failed."
|
||||
retrieve_build_log
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Copying build from container ..."
|
||||
echo -e "${BIYellow}---${RST} Creating container from pypeclub/openpype:$openpype_version ..."
|
||||
id="$(docker create -ti pypeclub/openpype:$openpype_version bash)"
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo -e "${BIRed}!!!${RST} Cannot create just built container."
|
||||
return 1
|
||||
fi
|
||||
create_container
|
||||
echo -e "${BIYellow}---${RST} Copying ..."
|
||||
docker cp "$id:/opt/openpype/build/exe.linux-x86_64-3.7" "$openpype_root/build"
|
||||
docker cp "$cid:/opt/openpype/build/exe.linux-x86_64-3.7" "$openpype_root/build"
|
||||
docker cp "$cid:/opt/openpype/build/build.log" "$openpype_root/build"
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo -e "${BIRed}!!!${RST} Copying failed."
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Fixing user ownership ..."
|
||||
username="$(logname)"
|
||||
local username="$(logname)"
|
||||
chown -R $username ./build
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} All done, you can delete container:"
|
||||
echo -e "${BIYellow}$id${RST}"
|
||||
echo -e "${BIYellow}$cid${RST}"
|
||||
}
|
||||
|
||||
return_code=0
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import hashlib
|
|||
import tarfile
|
||||
import zipfile
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
|
||||
term = blessed.Terminal()
|
||||
|
|
@ -65,12 +66,40 @@ def _print(msg: str, message_type: int = 0) -> None:
|
|||
|
||||
print("{}{}".format(header, msg))
|
||||
|
||||
|
||||
_print("Processing third-party dependencies ...")
|
||||
start_time = time.time_ns()
|
||||
openpype_root = Path(os.path.dirname(__file__)).parent
|
||||
pyproject = toml.load(openpype_root / "pyproject.toml")
|
||||
_print("Handling PySide2 Qt framework ...")
|
||||
pyside2_version = None
|
||||
try:
|
||||
pyside2_version = pyproject["openpype"]["pyside2"]["version"]
|
||||
_print("We'll install PySide2{}".format(pyside2_version))
|
||||
except AttributeError:
|
||||
_print("No PySide2 version was specified, using latest available.", 2)
|
||||
|
||||
pyside2_arg = "PySide2" if not pyside2_version else "PySide2{}".format(pyside2_version) # noqa: E501
|
||||
python_vendor_dir = openpype_root / "vendor" / "python"
|
||||
try:
|
||||
subprocess.run(
|
||||
[sys.executable, "-m", "pip", "install", "--upgrade",
|
||||
pyside2_arg, "-t", str(python_vendor_dir)],
|
||||
check=True, stdout=subprocess.DEVNULL)
|
||||
except subprocess.CalledProcessError as e:
|
||||
_print("Error during PySide2 installation.", 1)
|
||||
_print(str(e), 1)
|
||||
sys.exit(1)
|
||||
|
||||
# Remove libraries for QtSql which don't have available libraries
|
||||
# by default and Postgre library would require to modify rpath of dependency
|
||||
platform_name = platform.system().lower()
|
||||
if platform_name == "darwin":
|
||||
pyside2_sqldrivers_dir = (
|
||||
python_vendor_dir / "PySide2" / "Qt" / "plugins" / "sqldrivers"
|
||||
)
|
||||
for filepath in pyside2_sqldrivers_dir.iterdir():
|
||||
os.remove(str(filepath))
|
||||
|
||||
_print("Processing third-party dependencies ...")
|
||||
|
||||
try:
|
||||
thirdparty = pyproject["openpype"]["thirdparty"]
|
||||
|
|
|
|||
|
|
@ -98,4 +98,4 @@ main () {
|
|||
"$POETRY_HOME/bin/poetry" run python "$openpype_root/tools/fetch_thirdparty_libs.py"
|
||||
}
|
||||
|
||||
main
|
||||
main
|
||||
|
|
|
|||
44
tools/parse_pyproject.py
Normal file
44
tools/parse_pyproject.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Parse pyproject.toml and return its values.
|
||||
|
||||
Useful for shell scripts to know more about OpenPype build.
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import toml
|
||||
from pathlib import Path
|
||||
import click
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("keys", nargs=-1, type=click.STRING)
|
||||
def main(keys):
|
||||
"""Get values from `pyproject.toml`.
|
||||
|
||||
You can specify dot separated keys from `pyproject.toml`
|
||||
as arguments and this script will return them on separate
|
||||
lines. If key doesn't exists, None is returned.
|
||||
|
||||
"""
|
||||
openpype_root = Path(os.path.dirname(__file__)).parent
|
||||
py_project = toml.load(openpype_root / "pyproject.toml")
|
||||
for q in keys:
|
||||
query = q.split(".")
|
||||
data = py_project
|
||||
|
||||
for k in query:
|
||||
if isinstance(data, list):
|
||||
try:
|
||||
data = data[int(k)]
|
||||
except IndexError:
|
||||
print("None")
|
||||
sys.exit()
|
||||
continue
|
||||
|
||||
if isinstance(data, dict):
|
||||
data = data.get(k)
|
||||
print(data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -19,6 +19,7 @@ We use [CX_Freeze](https://cx-freeze.readthedocs.io/en/latest) to freeze the cod
|
|||
|
||||
This is outline of build steps. Most of them are done automatically via scripts:
|
||||
- Virtual environment is created using **Poetry** in `.venv`
|
||||
- Necessary python modules outside of `.venv` are stored to `./vendor/python` (like `PySide2`)
|
||||
- Necessary third-party tools (like [ffmpeg](https://www.ffmpeg.org/), [OpenImageIO](https://github.com/OpenImageIO/oiio)
|
||||
and [usd libraries](https://developer.nvidia.com/usd)) are downloaded to `./vendor/bin`
|
||||
- OpenPype code is frozen with **cx_freeze** to `./build`
|
||||
|
|
@ -55,14 +56,14 @@ For development purposes it is possible to run OpenPype directly from the source
|
|||
To start OpenPype from source you need to
|
||||
|
||||
1. Run `.\tools\create_env.ps1` to create virtual environment in `.venv`
|
||||
2. Run `.\tools\fetch_thirdparty_libs.ps1` to get **ffmpeg**, **oiio** and other tools needed.
|
||||
2. Run `.\tools\fetch_thirdparty_libs.ps1` to get **PySide2**, **ffmpeg**, **oiio** and other tools needed.
|
||||
3. Run `.\tools\run_tray.ps1` if you have all required dependencies on your machine you should be greeted with OpenPype igniter window and once you give it your Mongo URL, with OpenPype icon in the system tray.
|
||||
|
||||
Step 1 and 2 needs to be run only once (or when something was changed).
|
||||
|
||||
#### To build OpenPype:
|
||||
1. Run `.\tools\create_env.ps1` to create virtual environment in `.venv`
|
||||
2. Run `.\tools\fetch_thirdparty_libs.ps1` to get **ffmpeg**, **oiio** and other tools needed.
|
||||
2. Run `.\tools\fetch_thirdparty_libs.ps1` to get **PySide2**, **ffmpeg**, **oiio** and other tools needed.
|
||||
3. `.\tools\build.ps1` to build OpenPype to `.\build`
|
||||
|
||||
|
||||
|
|
@ -84,6 +85,13 @@ You can use Docker to build OpenPype. Just run:
|
|||
```shell
|
||||
$ sudo ./tools/docker_build.sh
|
||||
```
|
||||
|
||||
This will by default use Debian as base image. If you need to make Centos 7 compatible build, please run:
|
||||
|
||||
```sh
|
||||
sudo ./tools/docker_build.sh centos7
|
||||
```
|
||||
|
||||
and you should have built OpenPype in `build` directory. It is using **Centos 7**
|
||||
as a base image.
|
||||
|
||||
|
|
@ -178,7 +186,7 @@ For more information about setting your build environment please refer to [pyenv
|
|||
|
||||
#### To build Pype:
|
||||
1. Run `./tools/create_env.sh` to create virtual environment in `./venv`
|
||||
2. Run `./tools/fetch_thirdparty_libs.sh` to get **ffmpeg**, **oiio** and other tools needed.
|
||||
2. Run `./tools/fetch_thirdparty_libs.sh` to get **PySide2**, **ffmpeg**, **oiio** and other tools needed.
|
||||
3. Run `./tools/build.sh` to build pype executables in `.\build\`
|
||||
|
||||
</TabItem>
|
||||
|
|
@ -266,6 +274,19 @@ pywin32 = { version = "300", markers = "sys_platform == 'win32'" }
|
|||
|
||||
For more information see [Poetry documentation](https://python-poetry.org/docs/dependency-specification/).
|
||||
|
||||
### Python modules as thirdparty
|
||||
There are some python modules that can be available only in OpenPype and should not be propagated to any subprocess.
|
||||
Best example is **PySide2** which is required to run OpenPype but can be used only in OpenPype and should not be in PYTHONPATH for most of host applications.
|
||||
We've decided to separate these breaking dependencies to be able run OpenPype from code and from build the same way.
|
||||
|
||||
:::warning
|
||||
**PySide2** has handled special cases related to it's build process.
|
||||
### Linux
|
||||
- We're fixing rpath of shared objects on linux which is modified during cx freeze processing.
|
||||
### MacOS
|
||||
- **QtSql** libraries are removed on MacOS because their dependencies are not available and would require to modify rpath of Postgre library.
|
||||
:::
|
||||
|
||||
### Binary dependencies
|
||||
To add some binary tool or something that doesn't fit standard Python distribution methods, you
|
||||
can use [fetch_thirdparty_libs](#fetch_thirdparty_libs) script. It will take things defined in
|
||||
|
|
@ -323,14 +344,18 @@ Same as:
|
|||
poetry run python ./tools/create_zip.py
|
||||
```
|
||||
|
||||
### docker_build.sh
|
||||
### docker_build.sh *[variant]*
|
||||
Script to build OpenPype on [Docker](https://www.docker.com/) enabled systems - usually Linux and Windows
|
||||
with [Docker Desktop](https://docs.docker.com/docker-for-windows/install/)
|
||||
and [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/about) (WSL) installed.
|
||||
|
||||
It must be run with administrative privileges - `sudo ./docker_build.sh`.
|
||||
|
||||
It will use **Centos 7** base image to build OpenPype. You'll see your build in `./build` folder.
|
||||
It will use latest **Debian** base image to build OpenPype. If you need to build OpenPype for
|
||||
older systems like Centos 7, use `centos7` as argument. This will use another Dockerfile to build
|
||||
OpenPype with **Centos 7** as base image.
|
||||
|
||||
You'll see your build in `./build` folder.
|
||||
|
||||
### fetch_thirdparty_libs
|
||||
This script will download necessary tools for OpenPype defined in `pyproject.toml` like FFMpeg,
|
||||
|
|
|
|||
|
|
@ -116,8 +116,8 @@ module.exports = {
|
|||
// Optional: Algolia search parameters
|
||||
searchParameters: {},
|
||||
},
|
||||
googleAnalytics: {
|
||||
trackingID: 'G-HHJZ9VF0FG',
|
||||
gtag: {
|
||||
trackingID: 'G-DTKXMFENFY',
|
||||
// Optional fields.
|
||||
anonymizeIP: false, // Should IPs be anonymized?
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2175,11 +2175,11 @@ autoprefixer@^10.0.2, autoprefixer@^10.2.5:
|
|||
postcss-value-parser "^4.1.0"
|
||||
|
||||
axios@^0.21.1:
|
||||
version "0.21.1"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
|
||||
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
|
||||
version "0.21.4"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
|
||||
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
|
||||
dependencies:
|
||||
follow-redirects "^1.10.0"
|
||||
follow-redirects "^1.14.0"
|
||||
|
||||
babel-loader@^8.2.2:
|
||||
version "8.2.2"
|
||||
|
|
@ -3982,10 +3982,10 @@ flux@^4.0.1:
|
|||
fbemitter "^3.0.0"
|
||||
fbjs "^3.0.0"
|
||||
|
||||
follow-redirects@^1.0.0, follow-redirects@^1.10.0:
|
||||
version "1.13.3"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267"
|
||||
integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==
|
||||
follow-redirects@^1.0.0, follow-redirects@^1.14.0:
|
||||
version "1.14.4"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379"
|
||||
integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==
|
||||
|
||||
for-in@^1.0.2:
|
||||
version "1.0.2"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue