From 241ff2697ea87318767bc53e805153b5ff2c7165 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 8 Apr 2021 16:41:03 +0200 Subject: [PATCH 01/53] Dockerfile to build OpenPype using docker --- .dockerignore | 143 ++++++++++++++++++++++++++++++++++++ Dockerfile | 64 ++++++++++++++++ tools/build.ps1 | 18 +++-- tools/build.sh | 18 +++-- tools/create_env.ps1 | 20 +++-- tools/create_env.sh | 20 +++-- tools/create_zip.ps1 | 18 +++-- tools/create_zip.sh | 18 +++-- tools/docker_build.sh | 31 ++++++++ tools/make_docs.ps1 | 19 +++-- tools/make_docs.sh | 19 +++-- tools/run_mongo.ps1 | 18 +++-- tools/run_mongo.sh | 18 +++-- tools/run_settings.sh | 70 ++++-------------- tools/run_tests.ps1 | 18 +++-- tools/run_tests.sh | 18 +++-- tools/run_tray.sh | 66 ----------------- tools/update_submodules.ps1 | 18 +++-- tools/update_submodules.sh | 17 ++++- 19 files changed, 437 insertions(+), 194 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 tools/docker_build.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..c48c47562e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,143 @@ +# Created by .ignore support plugin (hsz.mobi) +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +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 + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +vendor/bin +docs/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..2499b26278 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,64 @@ +# 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 + +RUN yum -y update \ + && yum -y install epel-release centos-release-scl \ + && yum -y install \ + bash \ + which \ + git \ + devtoolset-7-gcc* \ + make \ + cmake \ + curl \ + wget \ + gcc \ + zlib-devel \ + bzip2 \ + bzip2-devel \ + readline-devel \ + sqlite sqlite-devel \ + openssl-devel \ + tk-devel libffi-devel \ + qt5-qtbase-devel \ + && yum clean all + +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 +RUN cat $HOME/.bashrc && source $HOME/.bashrc && pyenv install ${OPENPYPE_PYTHON_VERSION} + +COPY . /opt/openpype/ +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 \ + && bash ./tools/build.sh diff --git a/tools/build.ps1 b/tools/build.ps1 index 412bb111c1..783e531b83 100644 --- a/tools/build.ps1 +++ b/tools/build.ps1 @@ -76,11 +76,19 @@ function Install-Poetry() { $art = @" -▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀ -▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░ -▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄ - .---= [ by Pype Club ] =---. - https://openpype.io + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · "@ diff --git a/tools/build.sh b/tools/build.sh index b95e2969c4..68934f82a3 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -6,11 +6,19 @@ art () { cat <<-EOF -▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀ -▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░ -▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄ - .---= [ by Pype Club ] =---. - https://openpype.io + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · EOF } diff --git a/tools/create_env.ps1 b/tools/create_env.ps1 index 44e1799be8..7732576176 100644 --- a/tools/create_env.ps1 +++ b/tools/create_env.ps1 @@ -88,11 +88,21 @@ Set-Location -Path $openpype_root $art = @" -▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀ -▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░ -▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄ - .---= [ by Pype Club ] =---. - https://openpype.io + + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · + "@ diff --git a/tools/create_env.sh b/tools/create_env.sh index 7bdb8503fd..34f9b5af0f 100755 --- a/tools/create_env.sh +++ b/tools/create_env.sh @@ -7,11 +7,19 @@ art () { cat <<-EOF -▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀ -▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░ -▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄ - .---= [ by Pype Club ] =---. - https://openpype.io + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · EOF } @@ -169,7 +177,7 @@ main () { # cx_freeze will crash on missing __pychache__ on these but # reinstalling them solves the problem. echo -e "${BIGreen}>>>${RST} Fixing pycache bug ..." - poetry run python -m pip install --upgrade pip + poetry run python -m pip install --force-reinstall pip poetry run pip install --force-reinstall setuptools poetry run pip install --force-reinstall wheel poetry run python -m pip install --force-reinstall pip diff --git a/tools/create_zip.ps1 b/tools/create_zip.ps1 index d18806c40b..466c5315dd 100644 --- a/tools/create_zip.ps1 +++ b/tools/create_zip.ps1 @@ -41,11 +41,19 @@ Set-Location -Path $openpype_root $art = @" -▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀ -▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░ -▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄ - .---= [ by Pype Club ] =---. - https://openpype.io + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · "@ diff --git a/tools/create_zip.sh b/tools/create_zip.sh index 6e7f792f1d..044d2ab67d 100755 --- a/tools/create_zip.sh +++ b/tools/create_zip.sh @@ -8,11 +8,19 @@ art () { cat <<-EOF -▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀ -▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░ -▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄ - .---= [ by Pype Club ] =---. - https://openpype.io + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · EOF } diff --git a/tools/docker_build.sh b/tools/docker_build.sh new file mode 100644 index 0000000000..d64ca9b60e --- /dev/null +++ b/tools/docker_build.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +############################################################################## +# Return absolute path +# Globals: +# None +# Arguments: +# Path to resolve +# Returns: +# None +############################################################################### +realpath () { + echo $(cd $(dirname "$1"); pwd)/$(basename "$1") +} + +# Main +main () { + # Directories + + openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) + pushd "$openpype_root" > /dev/null || return > /dev/null + + version_command="import os;exec(open(os.path.join('$openpype_root', 'openpype', 'version.py')).read());print(__version__);" + openpype_version="$(python3 <<< ${version_command})" + + docker build -t pypeclub/openpype:$openpype_version . + id="$(docker create pypeclub/openpype:$openpype_version)" + docker cp "$id:/opt/openpype/build.linux-x86-64-3.7" "$openpype_root/build" +} + +main diff --git a/tools/make_docs.ps1 b/tools/make_docs.ps1 index aa526bbdc9..d72005f230 100644 --- a/tools/make_docs.ps1 +++ b/tools/make_docs.ps1 @@ -21,12 +21,19 @@ Set-Location -Path $openpype_root $art = @" - -▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀ -▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░ -▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄ - .---= [ by Pype Club ] =---. - https://openpype.io + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · "@ diff --git a/tools/make_docs.sh b/tools/make_docs.sh index 2ac12d3d95..bb2bbbfb83 100755 --- a/tools/make_docs.sh +++ b/tools/make_docs.sh @@ -7,11 +7,20 @@ art () { cat <<-EOF -▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀ -▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░ -▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄ - .---= [ by Pype Club ] =---. - https://openpype.io + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · + EOF } diff --git a/tools/run_mongo.ps1 b/tools/run_mongo.ps1 index 7e43a355e5..05fc497d34 100644 --- a/tools/run_mongo.ps1 +++ b/tools/run_mongo.ps1 @@ -13,11 +13,19 @@ PS> .\run_mongo.ps1 $art = @" -▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀ -▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░ -▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄ - .---= [ by Pype Club ] =---. - https://openpype.io + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · "@ diff --git a/tools/run_mongo.sh b/tools/run_mongo.sh index 1c788abcaf..15bff62b35 100755 --- a/tools/run_mongo.sh +++ b/tools/run_mongo.sh @@ -7,11 +7,19 @@ art () { cat <<-EOF -▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀ -▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░ -▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄ - .---= [ by Pype Club ] =---. - https://openpype.io + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · EOF } diff --git a/tools/run_settings.sh b/tools/run_settings.sh index 0c8a951d7c..aae5afdd96 100755 --- a/tools/run_settings.sh +++ b/tools/run_settings.sh @@ -6,11 +6,19 @@ art () { cat <<-EOF -▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀ -▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░ -▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄ - .---= [ by Pype Club ] =---. - https://openpype.io + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · EOF } @@ -49,53 +57,6 @@ BIPurple='\033[1;95m' # Purple BICyan='\033[1;96m' # Cyan BIWhite='\033[1;97m' # White - -############################################################################## -# Detect required version of python -# Globals: -# colors -# PYTHON -# Arguments: -# None -# Returns: -# None -############################################################################### -detect_python () { - echo -e "${BIGreen}>>>${RST} Using python \c" - local version_command="import sys;print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))" - local python_version="$(python3 <<< ${version_command})" - oIFS="$IFS" - IFS=. - set -- $python_version - IFS="$oIFS" - if [ "$1" -ge "3" ] && [ "$2" -ge "6" ] ; then - if [ "$2" -gt "7" ] ; then - echo -e "${BIWhite}[${RST} ${BIRed}$1.$2 ${BIWhite}]${RST} - ${BIRed}FAILED${RST} ${BIYellow}Version is new and unsupported, use${RST} ${BIPurple}3.7.x${RST}"; return 1; - else - echo -e "${BIWhite}[${RST} ${BIGreen}$1.$2${RST} ${BIWhite}]${RST}" - fi - else - command -v python3 >/dev/null 2>&1 || { echo -e "${BIRed}$1.$2$ - ${BIRed}FAILED${RST} ${BIYellow}Version is old and unsupported${RST}"; return 1; } - fi -} - -############################################################################## -# Clean pyc files in specified directory -# Globals: -# None -# Arguments: -# Optional path to clean -# Returns: -# None -############################################################################### -clean_pyc () { - local path - path=$oepnpype_root - echo -e "${BIGreen}>>>${RST} Cleaning pyc at [ ${BIWhite}$path${RST} ] ... \c" - find "$path" -regex '^.*\(__pycache__\|\.py[co]\)$' -delete - echo -e "${BIGreen}DONE${RST}" -} - ############################################################################## # Return absolute path # Globals: @@ -111,11 +72,6 @@ realpath () { # Main main () { - echo -e "${BGreen}" - art - echo -e "${RST}" - detect_python || return 1 - # Directories openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) pushd "$openpype_root" > /dev/null || return > /dev/null diff --git a/tools/run_tests.ps1 b/tools/run_tests.ps1 index 5070591c02..5a55f7acc7 100644 --- a/tools/run_tests.ps1 +++ b/tools/run_tests.ps1 @@ -32,11 +32,19 @@ function Show-PSWarning() { $art = @" -▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀ -▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░ -▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄ - .---= [ by Pype Club ] =---. - https://openpype.io + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · "@ diff --git a/tools/run_tests.sh b/tools/run_tests.sh index 0af052ca01..94deb1e065 100755 --- a/tools/run_tests.sh +++ b/tools/run_tests.sh @@ -6,11 +6,19 @@ art () { cat <<-EOF -▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀ -▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░ -▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄ - .---= [ by Pype Club ] =---. - https://openpype.io + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · EOF } diff --git a/tools/run_tray.sh b/tools/run_tray.sh index 8174f7e38a..d39920efb8 100755 --- a/tools/run_tray.sh +++ b/tools/run_tray.sh @@ -1,20 +1,6 @@ #!/usr/bin/env bash - # Run OpenPype Tray - -art () { - cat <<-EOF - -▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀ -▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░ -▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄ - .---= [ by Pype Club ] =---. - https://openpype.io - -EOF -} - # Colors for terminal RST='\033[0m' # Text Reset @@ -50,53 +36,6 @@ BICyan='\033[1;96m' # Cyan BIWhite='\033[1;97m' # White -############################################################################## -# Detect required version of python -# Globals: -# colors -# PYTHON -# Arguments: -# None -# Returns: -# None -############################################################################### -detect_python () { - echo -e "${BIGreen}>>>${RST} Using python \c" - local version_command="import sys;print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))" - local python_version="$(python3 <<< ${version_command})" - oIFS="$IFS" - IFS=. - set -- $python_version - IFS="$oIFS" - if [ "$1" -ge "3" ] && [ "$2" -ge "6" ] ; then - if [ "$2" -gt "7" ] ; then - echo -e "${BIWhite}[${RST} ${BIRed}$1.$2 ${BIWhite}]${RST} - ${BIRed}FAILED${RST} ${BIYellow}Version is new and unsupported, use${RST} ${BIPurple}3.7.x${RST}"; return 1; - else - echo -e "${BIWhite}[${RST} ${BIGreen}$1.$2${RST} ${BIWhite}]${RST}" - fi - PYTHON="python3" - else - command -v python3 >/dev/null 2>&1 || { echo -e "${BIRed}$1.$2$ - ${BIRed}FAILED${RST} ${BIYellow}Version is old and unsupported${RST}"; return 1; } - fi -} - -############################################################################## -# Clean pyc files in specified directory -# Globals: -# None -# Arguments: -# Optional path to clean -# Returns: -# None -############################################################################### -clean_pyc () { - local path - path=$openpype_root - echo -e "${BIGreen}>>>${RST} Cleaning pyc at [ ${BIWhite}$path${RST} ] ... \c" - find "$path" -regex '^.*\(__pycache__\|\.py[co]\)$' -delete - echo -e "${BIGreen}DONE${RST}" -} - ############################################################################## # Return absolute path # Globals: @@ -112,11 +51,6 @@ realpath () { # Main main () { - echo -e "${BGreen}" - art - echo -e "${RST}" - detect_python || return 1 - # Directories openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) pushd "$openpype_root" > /dev/null || return > /dev/null diff --git a/tools/update_submodules.ps1 b/tools/update_submodules.ps1 index d0f93d9f7e..8ecc278510 100644 --- a/tools/update_submodules.ps1 +++ b/tools/update_submodules.ps1 @@ -10,11 +10,19 @@ PS> .\update_submodules.ps1 $art = @" -▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀ -▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░ -▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄ - .---= [ by Pype Club ] =---. - https://openpype.io + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · "@ diff --git a/tools/update_submodules.sh b/tools/update_submodules.sh index 465827bfbb..49a3d08afb 100644 --- a/tools/update_submodules.sh +++ b/tools/update_submodules.sh @@ -6,10 +6,19 @@ art () { cat <<-EOF -▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀ -▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░ -▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄ - .---= [ by Pype Club ] =---. + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · EOF } From a2b8ee03cf541a62c34de01b0ea36d8adbec4bf8 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 8 Apr 2021 17:15:56 +0200 Subject: [PATCH 02/53] updated docs --- website/docs/dev_build.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/website/docs/dev_build.md b/website/docs/dev_build.md index 9523035705..114c896f44 100644 --- a/website/docs/dev_build.md +++ b/website/docs/dev_build.md @@ -1,6 +1,6 @@ --- id: dev_build -title: Build openPYPE from source +title: Build OpenPYPE from source sidebar_label: Build --- @@ -45,12 +45,12 @@ To start OpenPype from source you need to 2) 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. -### To build openPype: +### To build OpenPype: 1) Run `.\tools\create_env.ps1` to create virtual environment in `.\venv` 2) Run `.\tools\build.ps1` to build pype executables in `.\build\` -To create distributable openPype versions, run `./tools/create_zip.ps1` - that will +To create distributable OpenPype versions, run `./tools/create_zip.ps1` - that will create zip file with name `pype-vx.x.x.zip` parsed from current pype repository and copy it to user data dir. You can specify `--path /path/to/zip` to force it into a different location. This can be used to prepare new version releases for artists in the studio environment @@ -61,7 +61,17 @@ without the need to re-build the whole package -To build pype on linux you wil need: +#### Docker +You can use Docker to build OpenPype. Just run: +```sh +sudo ./tools/docker_build.sh +``` +and you should have built OpenPype in `build` directory. It is using **Centos 7** +as a base image. + + +#### Manual build +To build OpenPype on Linux you wil need: - **[curl](https://curl.se)** on systems that doesn't have one preinstalled. - Python header files installed (**python3-dev** on Ubuntu for example). @@ -143,7 +153,7 @@ pyenv local 3.7.9 To build pype on MacOS you wil need: - **[Homebrew](https://brew.sh)**, Easy way of installing everything necessary is to use. -- **[CMake](https://cmake.org/)** to build some external openPype dependencies. +- **[CMake](https://cmake.org/)** to build some external OpenPype dependencies. - **XCode Command Line Tools** (or some other build system) 1) Install **Homebrew**: From 29bc94dd8ec09b2a04f68bafbdeb037826556f18 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 8 Apr 2021 17:23:19 +0200 Subject: [PATCH 03/53] pull command in docs --- website/docs/dev_build.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/website/docs/dev_build.md b/website/docs/dev_build.md index 114c896f44..368388a1c0 100644 --- a/website/docs/dev_build.md +++ b/website/docs/dev_build.md @@ -69,6 +69,11 @@ sudo ./tools/docker_build.sh and you should have built OpenPype in `build` directory. It is using **Centos 7** as a base image. +You can pull the image: + +```sh +docker pull pypeclub/openpype:latest +``` #### Manual build To build OpenPype on Linux you wil need: From 9b9ee210ec731dd559c82733aabc967f10b9316a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Wed, 14 Apr 2021 10:29:21 +0200 Subject: [PATCH 04/53] remove latest tag on docker image update docker tags info --- website/docs/dev_build.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/docs/dev_build.md b/website/docs/dev_build.md index 368388a1c0..14efeaa850 100644 --- a/website/docs/dev_build.md +++ b/website/docs/dev_build.md @@ -72,8 +72,10 @@ as a base image. You can pull the image: ```sh -docker pull pypeclub/openpype:latest +# replace 3.0.0 tag with version you want +docker pull pypeclub/openpype:3.0.0 ``` +See https://hub.docker.com/r/pypeclub/openpype/tag for more. #### Manual build To build OpenPype on Linux you wil need: From 660be2d1d23c370302556363d342dd8bbb48558f Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 5 May 2021 17:30:49 +0200 Subject: [PATCH 05/53] resolve user and copying problems --- Dockerfile | 16 +++++++++------- tools/docker_build.sh | 22 ++++++++++++++++++---- 2 files changed, 27 insertions(+), 11 deletions(-) mode change 100644 => 100755 tools/docker_build.sh diff --git a/Dockerfile b/Dockerfile index 2499b26278..5dfd3fc61f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,8 @@ LABEL org.opencontainers.image.source="https://github.com/pypeclub/pype" USER root RUN yum -y update \ - && yum -y install epel-release centos-release-scl \ + && 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 \ @@ -29,12 +30,13 @@ RUN yum -y update \ openssl-devel \ tk-devel libffi-devel \ qt5-qtbase-devel \ + patchelf \ && yum clean all RUN mkdir /opt/openpype -RUN useradd -m pype -RUN chown pype /opt/openpype -USER pype +# RUN useradd -m pype +# RUN chown pype /opt/openpype +# USER pype RUN curl https://pyenv.run | bash ENV PYTHON_CONFIGURE_OPTS --enable-shared @@ -45,11 +47,11 @@ RUN echo 'export PATH="$HOME/.pyenv/bin:$PATH"'>> $HOME/.bashrc \ RUN cat $HOME/.bashrc && source $HOME/.bashrc && pyenv install ${OPENPYPE_PYTHON_VERSION} COPY . /opt/openpype/ -USER root -RUN chown -R pype /opt/openpype +# 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 +# USER pype WORKDIR /opt/openpype diff --git a/tools/docker_build.sh b/tools/docker_build.sh old mode 100644 new mode 100755 index d64ca9b60e..e3ce9a04c0 --- a/tools/docker_build.sh +++ b/tools/docker_build.sh @@ -1,5 +1,11 @@ #!/usr/bin/env bash +# Colors for terminal + +RST='\033[0m' # Text Reset +BIGreen='\033[1;92m' # Green +BIYellow='\033[1;93m' # Yellow + ############################################################################## # Return absolute path # Globals: @@ -15,17 +21,25 @@ realpath () { # Main main () { - # Directories - openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) pushd "$openpype_root" > /dev/null || return > /dev/null + 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})" + echo -e "${BIGreen}>>>${RST} Running docker build ..." docker build -t pypeclub/openpype:$openpype_version . - id="$(docker create pypeclub/openpype:$openpype_version)" - docker cp "$id:/opt/openpype/build.linux-x86-64-3.7" "$openpype_root/build" + + 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)" + echo -e "${BIYellow}---${RST} Copying ..." + docker cp "$id:/opt/openpype/build/exe.linux-x86_64-3.7" "$openpype_root/build" + echo -e "${BIGreen}>>>${RST} All done, you can delete container:" + echo -e "${BIYellow}$id${RST}" } main From 46fd091aec409d41e87e5c710ac6c496f7c42c2b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 10 May 2021 09:38:08 +0200 Subject: [PATCH 06/53] SyncServer - support for additional settings and local settings WIP --- .../providers/abstract_provider.py | 34 ++++++++++++++ .../modules/sync_server/providers/gdrive.py | 21 ++++++++- openpype/modules/sync_server/providers/lib.py | 4 +- .../sync_server/providers/local_drive.py | 15 ++++++ .../modules/sync_server/sync_server_module.py | 38 ++++++++++----- openpype/modules/sync_server/utils.py | 6 +++ openpype/settings/entities/__init__.py | 4 +- openpype/settings/entities/enum_entity.py | 46 +++++++++++++++++++ .../schemas/system_schema/schema_modules.json | 31 +++++++++++-- 9 files changed, 177 insertions(+), 22 deletions(-) diff --git a/openpype/modules/sync_server/providers/abstract_provider.py b/openpype/modules/sync_server/providers/abstract_provider.py index a60595ba93..7cd42fb4fa 100644 --- a/openpype/modules/sync_server/providers/abstract_provider.py +++ b/openpype/modules/sync_server/providers/abstract_provider.py @@ -7,6 +7,8 @@ log = Logger().get_logger("SyncServer") @six.add_metaclass(abc.ABCMeta) class AbstractProvider: + CODE = '' + LABEL = '' def __init__(self, project_name, site_name, tree=None, presets=None): self.presets = None @@ -25,6 +27,38 @@ class AbstractProvider: (boolean) """ + @abc.abstractmethod + def set_editable_properties(self): + """ + Sets dictionary of editable properties with scopes. + + Example: + { 'credentials_url': {'scopes': [utils.EditableScopes.SYSTEM], + 'type': 'text'}} + """ + + @abc.abstractmethod + def get_editable_properties(self, scopes): + """ + Returns filtered list of editable properties + + Args: + scopes (list) of utils.EditableScopes (optional - filter on) + + Returns: + (dict) + """ + if not scopes: + return self._editable_properties + + editable = {} + for scope in scopes: + for key, properties in self._editable_properties.items(): + if scope in properties['scope']: + editable[key] = properties + + return editable + @abc.abstractmethod def upload_file(self, source_path, path, server, collection, file, representation, site, diff --git a/openpype/modules/sync_server/providers/gdrive.py b/openpype/modules/sync_server/providers/gdrive.py index b67e5a6cfa..f2c18a04a9 100644 --- a/openpype/modules/sync_server/providers/gdrive.py +++ b/openpype/modules/sync_server/providers/gdrive.py @@ -7,7 +7,7 @@ from .abstract_provider import AbstractProvider from googleapiclient.http import MediaFileUpload, MediaIoBaseDownload from openpype.api import Logger from openpype.api import get_system_settings -from ..utils import time_function, ResumableError +from ..utils import time_function, ResumableError, EditableScopes import time @@ -42,9 +42,12 @@ class GDriveHandler(AbstractProvider): } } """ + CODE = 'gdrive' + LABEL = 'Google Drive' + FOLDER_STR = 'application/vnd.google-apps.folder' MY_DRIVE_STR = 'My Drive' # name of root folder of regular Google drive - CHUNK_SIZE = 2097152 # must be divisible by 256! + CHUNK_SIZE = 2097152 # must be divisible by 256! used for upload chunks def __init__(self, project_name, site_name, tree=None, presets=None): self.presets = None @@ -52,6 +55,8 @@ class GDriveHandler(AbstractProvider): self.project_name = project_name self.site_name = site_name + self._editable_properties = {} + self.presets = presets if not self.presets: log.info("Sync Server: There are no presets for {}.". @@ -73,6 +78,7 @@ class GDriveHandler(AbstractProvider): self._tree = tree self.active = True + self.set_editable_properties() def is_active(self): """ @@ -82,6 +88,17 @@ class GDriveHandler(AbstractProvider): """ return self.active + def set_editable_properties(self): + editable = { + 'credential_url': {'scope': [EditableScopes.PROJECT, + EditableScopes.LOCAL], + 'type': 'text'}, + + 'roots': {'scope': [EditableScopes.PROJECT], + 'type': 'dict'} + } + self._editable_properties = editable + def get_roots_config(self, anatomy=None): """ Returns root values for path resolving diff --git a/openpype/modules/sync_server/providers/lib.py b/openpype/modules/sync_server/providers/lib.py index 01a5d50ba5..f9c4309724 100644 --- a/openpype/modules/sync_server/providers/lib.py +++ b/openpype/modules/sync_server/providers/lib.py @@ -91,5 +91,5 @@ factory = ProviderFactory() # there is implementing 'GDriveHandler' class # 7 denotes number of files that could be synced in single loop - learned by # trial and error -factory.register_provider('gdrive', GDriveHandler, 7) -factory.register_provider('local_drive', LocalDriveHandler, 50) +factory.register_provider(GDriveHandler.CODE, GDriveHandler, 7) +factory.register_provider(LocalDriveHandler.CODE, LocalDriveHandler, 50) diff --git a/openpype/modules/sync_server/providers/local_drive.py b/openpype/modules/sync_server/providers/local_drive.py index 1f4fca80eb..2a96094f22 100644 --- a/openpype/modules/sync_server/providers/local_drive.py +++ b/openpype/modules/sync_server/providers/local_drive.py @@ -7,22 +7,37 @@ import time from openpype.api import Logger, Anatomy from .abstract_provider import AbstractProvider +from ..utils import EditableScopes + log = Logger().get_logger("SyncServer") class LocalDriveHandler(AbstractProvider): + CODE = 'local_drive' + LABEL = 'Local drive' + """ Handles required operations on mounted disks with OS """ def __init__(self, project_name, site_name, tree=None, presets=None): self.presets = None self.active = False self.project_name = project_name self.site_name = site_name + self._editable_properties = {} self.active = self.is_active() + self.set_editable_properties() def is_active(self): return True + def set_editable_properties(self): + editable = { + 'roots': {'scope': [EditableScopes.PROJECT, + EditableScopes.LOCAL], + 'type': 'dict'} + } + self._editable_properties = editable + def upload_file(self, source_path, target_path, server, collection, file, representation, site, overwrite=False, direction="Upload"): diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index b50bf19dca..e29861c20c 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -340,18 +340,6 @@ class SyncServerModule(PypeModule, ITrayModule): return self._get_enabled_sites_from_settings(sync_settings) - def get_configurable_items_for_site(self, project_name, site_name): - """ - Returns list of items that should be configurable by User - - Returns: - (list of dict) - [{key:"root", label:"root", value:"valueFromSettings"}] - """ - # if project_name is None: ..for get_default_project_settings - # return handler.get_configurable_items() - pass - def get_active_site(self, project_name): """ Returns active (mine) site for 'project_name' from settings @@ -402,6 +390,32 @@ class SyncServerModule(PypeModule, ITrayModule): return remote_site + def get_configurable_items(self): + pass + + def get_configurable_items_for_site(self, project_name, site_name): + """ + Returns list of items that should be configurable by User + + Returns: + (list of dict) + [{key:"root", label:"root", value:"valueFromSettings"}] + """ + # sites = set(self.get_active_sites(project_name), + # self.get_remote_sites(project_name)) + # for site in sites: + # if site_name + + def _get_configurable_items_for_project(self, project_name): + from .providers import lib + sites = set(self.get_active_sites(project_name), + self.get_remote_sites(project_name)) + editable = {} + for site in sites: + provider_name = self.get_provider_for_site(project_name, site) + + + def reset_timer(self): """ Called when waiting for next loop should be skipped. diff --git a/openpype/modules/sync_server/utils.py b/openpype/modules/sync_server/utils.py index fa6e63b029..d4fc29ff8a 100644 --- a/openpype/modules/sync_server/utils.py +++ b/openpype/modules/sync_server/utils.py @@ -33,3 +33,9 @@ def time_function(method): return result return timed + + +class EditableScopes: + SYSTEM = 0 + PROJECT = 1 + LOCAL = 2 diff --git a/openpype/settings/entities/__init__.py b/openpype/settings/entities/__init__.py index f76a915225..2c71b622ee 100644 --- a/openpype/settings/entities/__init__.py +++ b/openpype/settings/entities/__init__.py @@ -101,7 +101,8 @@ from .enum_entity import ( BaseEnumEntity, EnumEntity, AppsEnumEntity, - ToolsEnumEntity + ToolsEnumEntity, + ProvidersEnum ) from .list_entity import ListEntity @@ -149,6 +150,7 @@ __all__ = ( "EnumEntity", "AppsEnumEntity", "ToolsEnumEntity", + "ProvidersEnum", "ListEntity", diff --git a/openpype/settings/entities/enum_entity.py b/openpype/settings/entities/enum_entity.py index 693305cb1e..a5492cd727 100644 --- a/openpype/settings/entities/enum_entity.py +++ b/openpype/settings/entities/enum_entity.py @@ -217,3 +217,49 @@ class ToolsEnumEntity(BaseEnumEntity): if key in self.valid_keys: new_value.append(key) self._current_value = new_value + + +class ProvidersEnum(BaseEnumEntity): + schema_types = ["providers-enum"] + + def _item_initalization(self): + self.multiselection = False + self.value_on_not_set = "" + self.enum_items = [] + self.valid_keys = set() + self.valid_value_types = (str, ) + self.placeholder = None + + def _get_enum_values(self): + # from openpype.modules.sync_server.providers import lib as lib_providers + # + # providers = lib_providers.factory.providers + # + # valid_keys = set() + # enum_items = [] + # for provider_code, provider_info in providers.items(): + # provider, _ = provider_info + # enum_items.append({provider_code: provider.LABEL}) + # valid_keys.add(provider_code) + valid_keys = set() + enum_items = [] + if not valid_keys: + enum_items.append({'': 'N/A'}) + valid_keys.add('') + + return enum_items, valid_keys + + def set_override_state(self, *args, **kwargs): + super(ProvidersEnum, self).set_override_state(*args, **kwargs) + + self.enum_items, self.valid_keys = self._get_enum_values() + + value_on_not_set = list(self.valid_keys)[0] + if self._current_value is NOT_SET: + self._current_value = value_on_not_set + + self.value_on_not_set = value_on_not_set + + +# class ActiveSiteEnum +# class RemoteSiteEnum \ No newline at end of file diff --git a/openpype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json index 878958b12d..d1b498bb86 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_modules.json +++ b/openpype/settings/entities/schemas/system_schema/schema_modules.json @@ -85,11 +85,32 @@ "label": "Site Sync", "collapsible": true, "checkbox_key": "enabled", - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }] + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "dict-modifiable", + "collapsible": true, + "key": "sites", + "label": "Sites", + "collapsible_key": false, + "is_file": true, + "object_type": + { + "type": "dict", + "children": [ + { + "type": "providers-enum", + "key": "provider", + "label": "Provider" + } + ] + } + } + ] },{ "type": "dict", "key": "deadline", From 27d779159db9afbd1f107359d4ffecc54fbb715e Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 10 May 2021 23:00:18 +0200 Subject: [PATCH 07/53] fix pyenv path --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 5dfd3fc61f..ec053ce90a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,7 +43,8 @@ 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 virtualenv-init -)"' >> $HOME/.bashrc \ + && echo -e "eval \"$(pyenv init --path)\"\n$(cat $HOME/.profile)" > $HOME/.profile RUN cat $HOME/.bashrc && source $HOME/.bashrc && pyenv install ${OPENPYPE_PYTHON_VERSION} COPY . /opt/openpype/ From b401e0bb5454d8069c03b85d36012a9381c8751f Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 11 May 2021 10:20:10 +0200 Subject: [PATCH 08/53] removing python3 command --- Dockerfile | 12 +++++++++--- tools/build.sh | 14 +++++++------- tools/create_env.sh | 4 ++-- tools/docker_build.sh | 17 ++++++++++++++++- 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index ec053ce90a..92900c67cf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,8 +9,13 @@ LABEL org.opencontainers.image.source="https://github.com/pypeclub/pype" USER root -RUN yum -y update \ - && yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm \ +# 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 \ @@ -42,10 +47,11 @@ RUN curl https://pyenv.run | bash ENV PYTHON_CONFIGURE_OPTS --enable-shared RUN echo 'export PATH="$HOME/.pyenv/bin:$PATH"'>> $HOME/.bashrc \ + && echo "alias pyhon3='python'" >> $HOME/.bashrc \ && echo 'eval "$(pyenv init -)"' >> $HOME/.bashrc \ && echo 'eval "$(pyenv virtualenv-init -)"' >> $HOME/.bashrc \ && echo -e "eval \"$(pyenv init --path)\"\n$(cat $HOME/.profile)" > $HOME/.profile -RUN cat $HOME/.bashrc && source $HOME/.bashrc && pyenv install ${OPENPYPE_PYTHON_VERSION} +RUN source $HOME/.bashrc && pyenv install ${OPENPYPE_PYTHON_VERSION} COPY . /opt/openpype/ # USER root diff --git a/tools/build.sh b/tools/build.sh index 0510447e07..953d51bd81 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -73,7 +73,7 @@ detect_python () { local version_command version_command="import sys;print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))" local python_version - python_version="$(python3 <<< ${version_command})" + python_version="$(python <<< ${version_command})" oIFS="$IFS" IFS=. set -- $python_version @@ -85,7 +85,7 @@ detect_python () { echo -e "${BIWhite}[${RST} ${BIGreen}$1.$2${RST} ${BIWhite}]${RST}" fi else - command -v python3 >/dev/null 2>&1 || { echo -e "${BIRed}$1.$2$ - ${BIRed}FAILED${RST} ${BIYellow}Version is old and unsupported${RST}"; return 1; } + command -v python >/dev/null 2>&1 || { echo -e "${BIRed}$1.$2$ - ${BIRed}FAILED${RST} ${BIYellow}Version is old and unsupported${RST}"; return 1; } fi } @@ -131,7 +131,7 @@ realpath () { install_poetry () { echo -e "${BIGreen}>>>${RST} Installing Poetry ..." command -v curl >/dev/null 2>&1 || { echo -e "${BIRed}!!!${RST}${BIYellow} Missing ${RST}${BIBlue}curl${BIYellow} command.${RST}"; return 1; } - curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python3 - + curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python - } # Main @@ -146,7 +146,7 @@ main () { pushd "$openpype_root" > /dev/null || return > /dev/null version_command="import os;exec(open(os.path.join('$openpype_root', 'openpype', 'version.py')).read());print(__version__);" - openpype_version="$(python3 <<< ${version_command})" + openpype_version="$(python <<< ${version_command})" _inside_openpype_tool="1" @@ -177,11 +177,11 @@ main () { echo -e "${BIGreen}>>>${RST} Building ..." if [[ "$OSTYPE" == "linux-gnu"* ]]; then - poetry run python3 "$openpype_root/setup.py" build > "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return; } + poetry run python "$openpype_root/setup.py" build > "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return; } elif [[ "$OSTYPE" == "darwin"* ]]; then - poetry run python3 "$openpype_root/setup.py" bdist_mac > "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return; } + 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; } fi - poetry run python3 "$openpype_root/tools/build_dependencies.py" + poetry run python "$openpype_root/tools/build_dependencies.py" if [[ "$OSTYPE" == "darwin"* ]]; then # fix code signing issue diff --git a/tools/create_env.sh b/tools/create_env.sh index a29139d884..81f0b3b0b0 100755 --- a/tools/create_env.sh +++ b/tools/create_env.sh @@ -89,7 +89,7 @@ done detect_python () { echo -e "${BIGreen}>>>${RST} Using python \c" local version_command="import sys;print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))" - local python_version="$(python3 <<< ${version_command})" + local python_version="$(python <<< ${version_command})" oIFS="$IFS" IFS=. set -- $python_version @@ -101,7 +101,7 @@ detect_python () { echo -e "${BIWhite}[${RST} ${BIGreen}$1.$2${RST} ${BIWhite}]${RST}" fi else - command -v python3 >/dev/null 2>&1 || { echo -e "${BIRed}$1.$2$ - ${BIRed}FAILED${RST} ${BIYellow}Version is old and unsupported${RST}"; return 1; } + command -v python >/dev/null 2>&1 || { echo -e "${BIRed}$1.$2$ - ${BIRed}FAILED${RST} ${BIYellow}Version is old and unsupported${RST}"; return 1; } fi } diff --git a/tools/docker_build.sh b/tools/docker_build.sh index e3ce9a04c0..e7caa57bcc 100755 --- a/tools/docker_build.sh +++ b/tools/docker_build.sh @@ -5,6 +5,7 @@ RST='\033[0m' # Text Reset BIGreen='\033[1;92m' # Green BIYellow='\033[1;93m' # Yellow +BIRed='\033[1;91m' # Red ############################################################################## # Return absolute path @@ -32,14 +33,28 @@ main () { echo -e "${BIGreen}>>>${RST} Running docker build ..." docker build -t pypeclub/openpype:$openpype_version . + if [ $? -ne 0 ] ; then + echo -e "${BIRed}!!!${RST} Docker build failed." + 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 echo -e "${BIYellow}---${RST} Copying ..." docker cp "$id:/opt/openpype/build/exe.linux-x86_64-3.7" "$openpype_root/build" + if [ $? -ne 0 ] ; then + echo -e "${BIRed}!!!${RST} Copying failed." + return 1 + fi echo -e "${BIGreen}>>>${RST} All done, you can delete container:" echo -e "${BIYellow}$id${RST}" } -main +return_code=0 +main || return_code=$? +exit $return_code From 2bc20c00a2a8b83508934943d96530e120502aec Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 11 May 2021 10:37:36 +0200 Subject: [PATCH 09/53] add fetch dependencies and its cleanup --- .dockerignore | 1 + Dockerfile | 9 +++++++++ tools/create_env.sh | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index c48c47562e..f8cacebbbc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -141,3 +141,4 @@ cython_debug/ vendor/bin docs/ +website/ diff --git a/Dockerfile b/Dockerfile index 92900c67cf..8d69a97b0e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -67,7 +67,16 @@ RUN cd /opt/openpype \ && pyenv local ${OPENPYPE_PYTHON_VERSION} RUN source $HOME/.bashrc \ + cd /opt/openpype \ && ./tools/create_env.sh RUN source $HOME/.bashrc \ + cd /opt/openpype \ + && ./tools/fetch_thirdparty_libs.sh + +RUN source $HOME/.bashrc \ + cd /opt/openpype \ && bash ./tools/build.sh + +RUN cd /opt/openpype \ + rm -rf ./vendor/bin diff --git a/tools/create_env.sh b/tools/create_env.sh index 81f0b3b0b0..b3fbaf42ed 100755 --- a/tools/create_env.sh +++ b/tools/create_env.sh @@ -108,7 +108,7 @@ detect_python () { install_poetry () { echo -e "${BIGreen}>>>${RST} Installing Poetry ..." command -v curl >/dev/null 2>&1 || { echo -e "${BIRed}!!!${RST}${BIYellow} Missing ${RST}${BIBlue}curl${BIYellow} command.${RST}"; return 1; } - curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python3 - + curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python - export PATH="$PATH:$HOME/.poetry/bin" } From a9d3acb99d5f0547ff4a8e3391577ce1473b8c92 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 11 May 2021 10:41:07 +0200 Subject: [PATCH 10/53] pyenv fixes --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 8d69a97b0e..b4241fbb83 100644 --- a/Dockerfile +++ b/Dockerfile @@ -68,14 +68,17 @@ RUN cd /opt/openpype \ RUN source $HOME/.bashrc \ cd /opt/openpype \ + && pyenv local ${OPENPYPE_PYTHON_VERSION} \ && ./tools/create_env.sh RUN source $HOME/.bashrc \ cd /opt/openpype \ + && pyenv local ${OPENPYPE_PYTHON_VERSION} \ && ./tools/fetch_thirdparty_libs.sh RUN source $HOME/.bashrc \ cd /opt/openpype \ + && pyenv local ${OPENPYPE_PYTHON_VERSION} \ && bash ./tools/build.sh RUN cd /opt/openpype \ From e4b51604ad9002e04138dbc59a36f5f481428d7b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 11 May 2021 12:12:39 +0200 Subject: [PATCH 11/53] SyncServer - sync_project_settings now contains all projects, must skip disabled explicitly --- openpype/modules/sync_server/sync_server.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 9b305a1b2e..28169ca8b3 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -274,6 +274,9 @@ class SyncServerThread(threading.Thread): self.module.set_sync_project_settings() # clean cache for collection, preset in self.module.sync_project_settings.\ items(): + if collection not in self.module.get_enabled_projects(): + continue + start_time = time.time() local_site, remote_site = self._working_sites(collection) if not all([local_site, remote_site]): From 2dccb8e7702c9fba763f1312c00fd20b36adaa3f Mon Sep 17 00:00:00 2001 From: Ondrej Date: Tue, 11 May 2021 14:11:32 +0200 Subject: [PATCH 12/53] corrected 3rd party bins --- Dockerfile | 9 +-------- pyproject.toml | 6 +++--- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index b4241fbb83..2309585a1b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,10 +47,9 @@ RUN curl https://pyenv.run | bash ENV PYTHON_CONFIGURE_OPTS --enable-shared RUN echo 'export PATH="$HOME/.pyenv/bin:$PATH"'>> $HOME/.bashrc \ - && echo "alias pyhon3='python'" >> $HOME/.bashrc \ && echo 'eval "$(pyenv init -)"' >> $HOME/.bashrc \ && echo 'eval "$(pyenv virtualenv-init -)"' >> $HOME/.bashrc \ - && echo -e "eval \"$(pyenv init --path)\"\n$(cat $HOME/.profile)" > $HOME/.profile + && echo 'eval "$(pyenv init --path)"' >> $HOME/.bashrc RUN source $HOME/.bashrc && pyenv install ${OPENPYPE_PYTHON_VERSION} COPY . /opt/openpype/ @@ -67,18 +66,12 @@ RUN cd /opt/openpype \ && pyenv local ${OPENPYPE_PYTHON_VERSION} RUN source $HOME/.bashrc \ - cd /opt/openpype \ - && pyenv local ${OPENPYPE_PYTHON_VERSION} \ && ./tools/create_env.sh RUN source $HOME/.bashrc \ - cd /opt/openpype \ - && pyenv local ${OPENPYPE_PYTHON_VERSION} \ && ./tools/fetch_thirdparty_libs.sh RUN source $HOME/.bashrc \ - cd /opt/openpype \ - && pyenv local ${OPENPYPE_PYTHON_VERSION} \ && bash ./tools/build.sh RUN cd /opt/openpype \ diff --git a/pyproject.toml b/pyproject.toml index c874db34f8..1f78fe1fc3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,9 +97,9 @@ url = "https://distribute.openpype.io/thirdparty/oiio_tools-2.2.0-windows.zip" hash = "fd2e00278e01e85dcee7b4a6969d1a16f13016ec16700fb0366dbb1b1f3c37ad" [openpype.thirdparty.oiio.linux] -url = "https://distribute.openpype.io/thirdparty/oiio-2.2.0-linux.tgz" -hash = "sha256:..." +url = "https://distribute.openpype.io/thirdparty/oiio_tools-2.2.12-linux.tgz" +hash = "53dea3e5b1acbe899268de39dd43685acd702f0f187ff37f65aa6349d22676a3" [openpype.thirdparty.oiio.darwin] url = "https://distribute.openpype.io/thirdparty/oiio-2.2.0-darwin.tgz" -hash = "sha256:..." \ No newline at end of file +hash = "sha256:..." From 49e44f0d70af849d2133d33fdffcbbd05cbb8cb4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 11 May 2021 14:43:26 +0200 Subject: [PATCH 13/53] SyncServer - check configured sites more effectively --- openpype/modules/sync_server/sync_server.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 28169ca8b3..b89eeebf19 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -206,10 +206,10 @@ def _get_configured_sites_from_setting(module, project_name, project_setting): all_sites = module._get_default_site_configs() all_sites.update(project_setting.get("sites")) for site_name, config in all_sites.items(): - handler = initiated_handlers. \ - get((config["provider"], site_name)) + provider = module.get_provider_for_site(site=site_name) + handler = initiated_handlers.get((provider, site_name)) if not handler: - handler = lib.factory.get_provider(config["provider"], + handler = lib.factory.get_provider(provider, project_name, site_name, presets=config) @@ -454,8 +454,9 @@ class SyncServerThread(threading.Thread): remote_site)) return None, None - if not all([site_is_working(self.module, collection, local_site), - site_is_working(self.module, collection, remote_site)]): + configured_sites = _get_configured_sites(self.module, collection) + if not all([local_site in configured_sites, + remote_site in configured_sites]): log.debug("Some of the sites {} - {} is not ".format(local_site, remote_site) + "working properly") From 1aafb697e44888074de1abad59bde216429d459e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 11 May 2021 14:49:13 +0200 Subject: [PATCH 14/53] SyncServer - change settings to new format Sites are configured in System Schemas and defaults were modified All providers carry dict of modifiable properties for Local Settings --- .../providers/abstract_provider.py | 27 +-- .../modules/sync_server/providers/gdrive.py | 55 ++++-- openpype/modules/sync_server/providers/lib.py | 11 ++ .../sync_server/providers/local_drive.py | 19 +- .../modules/sync_server/sync_server_module.py | 180 +++++++++++++----- openpype/modules/sync_server/tray/lib.py | 2 +- openpype/modules/sync_server/tray/widgets.py | 2 +- .../defaults/project_settings/global.json | 7 - .../defaults/system_settings/modules.json | 3 +- openpype/settings/entities/enum_entity.py | 28 +-- .../schema_project_syncserver.json | 5 - 11 files changed, 212 insertions(+), 127 deletions(-) diff --git a/openpype/modules/sync_server/providers/abstract_provider.py b/openpype/modules/sync_server/providers/abstract_provider.py index 7cd42fb4fa..2e9632134c 100644 --- a/openpype/modules/sync_server/providers/abstract_provider.py +++ b/openpype/modules/sync_server/providers/abstract_provider.py @@ -27,37 +27,16 @@ class AbstractProvider: (boolean) """ + @classmethod @abc.abstractmethod - def set_editable_properties(self): + def get_configurable_items(cls): """ - Sets dictionary of editable properties with scopes. + Returns filtered dict of editable properties - Example: - { 'credentials_url': {'scopes': [utils.EditableScopes.SYSTEM], - 'type': 'text'}} - """ - - @abc.abstractmethod - def get_editable_properties(self, scopes): - """ - Returns filtered list of editable properties - - Args: - scopes (list) of utils.EditableScopes (optional - filter on) Returns: (dict) """ - if not scopes: - return self._editable_properties - - editable = {} - for scope in scopes: - for key, properties in self._editable_properties.items(): - if scope in properties['scope']: - editable[key] = properties - - return editable @abc.abstractmethod def upload_file(self, source_path, path, diff --git a/openpype/modules/sync_server/providers/gdrive.py b/openpype/modules/sync_server/providers/gdrive.py index f2c18a04a9..e79927590b 100644 --- a/openpype/modules/sync_server/providers/gdrive.py +++ b/openpype/modules/sync_server/providers/gdrive.py @@ -1,22 +1,32 @@ from __future__ import print_function import os.path -from googleapiclient.discovery import build -import google.oauth2.service_account as service_account -from googleapiclient import errors -from .abstract_provider import AbstractProvider -from googleapiclient.http import MediaFileUpload, MediaIoBaseDownload +import time +import sys +from setuptools.extern import six + from openpype.api import Logger from openpype.api import get_system_settings +from .abstract_provider import AbstractProvider from ..utils import time_function, ResumableError, EditableScopes -import time +log = Logger().get_logger("SyncServer") + +try: + from googleapiclient.discovery import build + import google.oauth2.service_account as service_account + from googleapiclient import errors + from googleapiclient.http import MediaFileUpload, MediaIoBaseDownload +except (ImportError, SyntaxError): + if six.PY3: + six.reraise(*sys.exc_info()) + + # handle imports from Python 2 hosts - in those only basic methods are used + log.warning("Import failed, imported from Python 2, operations will fail.") SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly', 'https://www.googleapis.com/auth/drive.file', 'https://www.googleapis.com/auth/drive.readonly'] # for write|delete -log = Logger().get_logger("SyncServer") - class GDriveHandler(AbstractProvider): """ @@ -54,8 +64,7 @@ class GDriveHandler(AbstractProvider): self.active = False self.project_name = project_name self.site_name = site_name - - self._editable_properties = {} + self.service = None self.presets = presets if not self.presets: @@ -63,7 +72,7 @@ class GDriveHandler(AbstractProvider): format(site_name)) return - if not os.path.exists(self.presets["credentials_url"]): + if not os.path.exists(self.presets.get("credentials_url", "")): log.info("Sync Server: No credentials for Gdrive provider! ") return @@ -78,7 +87,6 @@ class GDriveHandler(AbstractProvider): self._tree = tree self.active = True - self.set_editable_properties() def is_active(self): """ @@ -86,18 +94,29 @@ class GDriveHandler(AbstractProvider): Returns: (boolean) """ - return self.active + return self.service is not None - def set_editable_properties(self): + @classmethod + def get_configurable_items(cls): + """ + Returns filtered dict of editable properties. + + + Returns: + (dict) + """ editable = { + # credentials could be override on Project or User level 'credential_url': {'scope': [EditableScopes.PROJECT, EditableScopes.LOCAL], + 'label': "Credentials url", 'type': 'text'}, - - 'roots': {'scope': [EditableScopes.PROJECT], - 'type': 'dict'} + # roots could be override only on Project leve, User cannot + 'root': {'scope': [EditableScopes.PROJECT], + 'label': "Roots", + 'type': 'dict'} } - self._editable_properties = editable + return editable def get_roots_config(self, anatomy=None): """ diff --git a/openpype/modules/sync_server/providers/lib.py b/openpype/modules/sync_server/providers/lib.py index f9c4309724..816ccca981 100644 --- a/openpype/modules/sync_server/providers/lib.py +++ b/openpype/modules/sync_server/providers/lib.py @@ -65,6 +65,17 @@ class ProviderFactory: info = self._get_creator_info(provider) return info[1] + def get_provider_configurable_items(self, provider): + """ + Returns dict of modifiable properties for 'provider'. + + Provider contains information which its properties and on what + level could be override + """ + provider_info = self._get_creator_info(provider) + + return provider_info[0].get_configurable_items() + def _get_creator_info(self, provider): """ Collect all necessary info for provider. Currently only creator diff --git a/openpype/modules/sync_server/providers/local_drive.py b/openpype/modules/sync_server/providers/local_drive.py index 2a96094f22..2d37d0e1c4 100644 --- a/openpype/modules/sync_server/providers/local_drive.py +++ b/openpype/modules/sync_server/providers/local_drive.py @@ -25,18 +25,25 @@ class LocalDriveHandler(AbstractProvider): self._editable_properties = {} self.active = self.is_active() - self.set_editable_properties() def is_active(self): return True - def set_editable_properties(self): + @classmethod + def get_configurable_items(cls): + """ + Returns filtered dict of editable properties + + Returns: + (dict) + """ editable = { - 'roots': {'scope': [EditableScopes.PROJECT, - EditableScopes.LOCAL], - 'type': 'dict'} + 'root': {'scope': [EditableScopes.PROJECT, + EditableScopes.LOCAL], + 'label': "Roots", + 'type': 'dict'} } - self._editable_properties = editable + return editable def upload_file(self, source_path, target_path, server, collection, file, representation, site, diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index e29861c20c..1e12db84a1 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -9,10 +9,12 @@ from .. import PypeModule, ITrayModule from openpype.api import ( Anatomy, get_project_settings, + get_system_settings, get_local_site_id) from openpype.lib import PypeLogger from .providers.local_drive import LocalDriveHandler +from .providers import lib from .utils import time_function, SyncStatus @@ -390,31 +392,99 @@ class SyncServerModule(PypeModule, ITrayModule): return remote_site - def get_configurable_items(self): - pass - - def get_configurable_items_for_site(self, project_name, site_name): + def get_configurable_items(self, scope=None): """ - Returns list of items that should be configurable by User + Returns list of items that could be configurable for all projects. + + Could be filtered by 'scope' argument (list) + + Args: + scope (list of utils.EditableScope) (optional) Returns: - (list of dict) - [{key:"root", label:"root", value:"valueFromSettings"}] + (dict of dict) + {projectA: { + siteA : { + key:"root", label:"root", value:"valueFromSettings" + } + } """ - # sites = set(self.get_active_sites(project_name), - # self.get_remote_sites(project_name)) - # for site in sites: - # if site_name - - def _get_configurable_items_for_project(self, project_name): - from .providers import lib - sites = set(self.get_active_sites(project_name), - self.get_remote_sites(project_name)) editable = {} - for site in sites: - provider_name = self.get_provider_for_site(project_name, site) + for project in self.connection.projects(): + project_name = project["name"] + items = self.get_configurable_items_for_project(project_name, + scope) + editable.update(items) + return editable + def get_configurable_items_for_project(self, project_name, scope=None): + """ + Returns list of items that could be configurable for specific + 'project_name' + + Args: + project_name (str) + scope (list of utils.EditableScope) (optional) + + Returns: + (dict of dict) + {projectA: { + siteA : { + key:"root", label:"root", value:"valueFromSettings" + } + } + """ + sites = set(self.get_active_sites(project_name)) | \ + set(self.get_remote_sites(project_name)) + editable = {} + for site_name in sites: + items = self.get_configurable_items_for_site(project_name, + site_name, + scope) + editable.update(items) + + return editable + + def get_configurable_items_for_site(self, project_name, site_name, + scope=None): + """ + Returns list of items that could be configurable. + + Args: + project_name (str) + site_name (str) + scope (list of utils.EditableScope) (optional) + + Returns: + (dict of dict) + {projectA: { + siteA : { + key:"root", label:"root", value:"valueFromSettings" + } + } + """ + provider_name = self.get_provider_for_site(site=site_name) + items = lib.factory.get_provider_configurable_items(provider_name, + scope) + + if not scope: + return {project_name: {site_name: items}} + + editable = {} + sync_s = self.get_sync_project_setting(project_name, True) + for scope in set([scope]): + for key, properties in items.items(): + if scope in properties['scope']: + val = sync_s.get("sites", {}).get(site_name, {}).get(key) + editable = { + "key": key, + "value": val, + "label": properties.get("label"), + "type": properties.get("type"), + } + + return {project_name: {site_name: editable}} def reset_timer(self): """ @@ -432,7 +502,7 @@ class SyncServerModule(PypeModule, ITrayModule): for project in self.connection.projects(): project_name = project["name"] project_settings = self.get_sync_project_setting(project_name) - if project_settings: + if project_settings and project_settings.get("enabled"): enabled_projects.append(project_name) return enabled_projects @@ -584,55 +654,60 @@ class SyncServerModule(PypeModule, ITrayModule): return self._sync_project_settings - def set_sync_project_settings(self): + def set_sync_project_settings(self, exclude_locals=False): """ Set sync_project_settings for all projects (caching) - + Args: + exclude_locals (bool): ignore overrides from Local Settings For performance """ sync_project_settings = {} + # sites are now configured system wide + sys_sett = get_system_settings() + sync_sett = sys_sett["modules"].get("sync_server") + system_sites = {} + for site, detail in sync_sett.get("sites", {}).items(): + system_sites[site] = detail + for collection in self.connection.database.collection_names(False): sync_settings = self._parse_sync_settings_from_settings( - get_project_settings(collection)) - if sync_settings: - default_sites = self._get_default_site_configs() - sync_settings['sites'].update(default_sites) - sync_project_settings[collection] = sync_settings + get_project_settings(collection, + exclude_locals=exclude_locals)) + + default_sites = self._get_default_site_configs() + sync_settings['sites'].update(default_sites) + sync_settings['sites'].update(system_sites) + sync_project_settings[collection] = sync_settings if not sync_project_settings: log.info("No enabled and configured projects for sync.") self._sync_project_settings = sync_project_settings - def get_sync_project_setting(self, project_name): + def get_sync_project_setting(self, project_name, exclude_locals=False): """ Handles pulling sync_server's settings for enabled 'project_name' Args: project_name (str): used in project settings + exclude_locals (bool): ignore overrides from Local Settings Returns: (dict): settings dictionary for the enabled project, empty if no settings or sync is disabled """ # presets set already, do not call again and again # self.log.debug("project preset {}".format(self.presets)) - if self.sync_project_settings and \ - self.sync_project_settings.get(project_name): - return self.sync_project_settings.get(project_name) + if not self.sync_project_settings or \ + not self.sync_project_settings.get(project_name): + self.set_sync_project_settings(project_name, exclude_locals) - settings = get_project_settings(project_name) - return self._parse_sync_settings_from_settings(settings) + return self.sync_project_settings.get(project_name) def _parse_sync_settings_from_settings(self, settings): """ settings from api.get_project_settings, TOOD rename """ sync_settings = settings.get("global").get("sync_server") - if not sync_settings: - log.info("No project setting not syncing.") - return {} - if sync_settings.get("enabled"): - return sync_settings - return {} + return sync_settings def _get_default_site_configs(self): """ @@ -643,16 +718,29 @@ class SyncServerModule(PypeModule, ITrayModule): get_local_site_id(): default_config} return all_sites - def get_provider_for_site(self, project_name, site): + def get_provider_for_site(self, project_name=None, site=None): """ - Return provider name for site. + Return provider name for site (unique name across all projects. """ - site_preset = self.get_sync_project_setting(project_name)["sites"].\ - get(site) - if site_preset: - return site_preset["provider"] + sites = {self.DEFAULT_SITE: "local_drive", + self.LOCAL_SITE: "local_drive", + get_local_site_id(): "local_drive"} - return "NA" + if site in sites.keys(): + return sites[site] + + if project_name: # backward compatibility + proj_settings = self.get_sync_project_setting(project_name) + provider = proj_settings.get("sites", {}).get(site, {}).\ + get("provider") + return provider + + sys_sett = get_system_settings() + sync_sett = sys_sett["modules"].get("sync_server") + for site, detail in sync_sett.get("sites", {}).items(): + sites[site] = detail.get("provider") + + return sites.get(site, 'N/A') @time_function def get_sync_representations(self, collection, active_site, remote_site): @@ -1130,7 +1218,7 @@ class SyncServerModule(PypeModule, ITrayModule): format(site_name)) return - provider_name = self.get_provider_for_site(collection, site_name) + provider_name = self.get_provider_for_site(site=site_name) if provider_name == 'local_drive': query = { diff --git a/openpype/modules/sync_server/tray/lib.py b/openpype/modules/sync_server/tray/lib.py index c1f8eaf629..25c600abd2 100644 --- a/openpype/modules/sync_server/tray/lib.py +++ b/openpype/modules/sync_server/tray/lib.py @@ -158,7 +158,7 @@ def translate_provider_for_icon(sync_server, project, site): """ if site == sync_server.DEFAULT_SITE: return sync_server.DEFAULT_SITE - return sync_server.get_provider_for_site(project, site) + return sync_server.get_provider_for_site(site=site) def get_item_by_id(model, object_id): diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index e80f91e09f..eae912206e 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -236,7 +236,7 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): for site, progress in {active_site: local_progress, remote_site: remote_progress}.items(): - provider = self.sync_server.get_provider_for_site(project, site) + provider = self.sync_server.get_provider_for_site(site=site) if provider == 'local_drive': if 'studio' in site: txt = " studio version" diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 48b7a24b0d..1f54bed03c 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -267,13 +267,6 @@ "remote_site": "studio" }, "sites": { - "gdrive": { - "provider": "gdrive", - "credentials_url": "", - "root": { - "work": "" - } - } } }, "project_plugins": { diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 6e4b493116..5c4aa6c485 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -135,7 +135,8 @@ "workspace_name": "" }, "sync_server": { - "enabled": false + "enabled": false, + "sites": {} }, "deadline": { "enabled": true, diff --git a/openpype/settings/entities/enum_entity.py b/openpype/settings/entities/enum_entity.py index a5492cd727..c6021b68de 100644 --- a/openpype/settings/entities/enum_entity.py +++ b/openpype/settings/entities/enum_entity.py @@ -231,21 +231,17 @@ class ProvidersEnum(BaseEnumEntity): self.placeholder = None def _get_enum_values(self): - # from openpype.modules.sync_server.providers import lib as lib_providers - # - # providers = lib_providers.factory.providers - # - # valid_keys = set() - # enum_items = [] - # for provider_code, provider_info in providers.items(): - # provider, _ = provider_info - # enum_items.append({provider_code: provider.LABEL}) - # valid_keys.add(provider_code) + from openpype.modules.sync_server.providers import lib as lib_providers + + providers = lib_providers.factory.providers + valid_keys = set() - enum_items = [] - if not valid_keys: - enum_items.append({'': 'N/A'}) - valid_keys.add('') + valid_keys.add('') + enum_items = [{'': 'Choose Provider'}] + for provider_code, provider_info in providers.items(): + provider, _ = provider_info + enum_items.append({provider_code: provider.LABEL}) + valid_keys.add(provider_code) return enum_items, valid_keys @@ -259,7 +255,3 @@ class ProvidersEnum(BaseEnumEntity): self._current_value = value_on_not_set self.value_on_not_set = value_on_not_set - - -# class ActiveSiteEnum -# class RemoteSiteEnum \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_syncserver.json b/openpype/settings/entities/schemas/projects_schema/schema_project_syncserver.json index ea1b8fc9da..bb5ebea45a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_syncserver.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_syncserver.json @@ -49,11 +49,6 @@ { "type": "dict", "children": [ - { - "type": "text", - "key": "provider", - "label": "Provider" - }, { "type": "text", "key": "credentials_url", From c778010fd4b905323b246e3025dac39b3667c3d8 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 11 May 2021 14:55:07 +0200 Subject: [PATCH 15/53] poetry fixes --- .dockerignore | 4 +- Dockerfile | 1 + tools/create_env.sh | 3 +- tools/docker_build.sh | 2 +- tools/fetch_thirdparty_libs.sh | 70 ++++++++-------------------------- 5 files changed, 22 insertions(+), 58 deletions(-) diff --git a/.dockerignore b/.dockerignore index f8cacebbbc..07c1c151ce 100644 --- a/.dockerignore +++ b/.dockerignore @@ -139,6 +139,8 @@ dmypy.json # Cython debug symbols cython_debug/ -vendor/bin +.poetry/ +.github/ +vendor/bin/ docs/ website/ diff --git a/Dockerfile b/Dockerfile index 2309585a1b..6a4cd3599d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -53,6 +53,7 @@ RUN echo 'export PATH="$HOME/.pyenv/bin:$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 diff --git a/tools/create_env.sh b/tools/create_env.sh index b3fbaf42ed..d6a6828718 100755 --- a/tools/create_env.sh +++ b/tools/create_env.sh @@ -107,9 +107,10 @@ detect_python () { install_poetry () { echo -e "${BIGreen}>>>${RST} Installing Poetry ..." + export POETRY_HOME="$openpype_root/.poetry" command -v curl >/dev/null 2>&1 || { echo -e "${BIRed}!!!${RST}${BIYellow} Missing ${RST}${BIBlue}curl${BIYellow} command.${RST}"; return 1; } curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python - - export PATH="$PATH:$HOME/.poetry/bin" + export PATH="$PATH:$POETRY_HOME/bin" } ############################################################################## diff --git a/tools/docker_build.sh b/tools/docker_build.sh index e7caa57bcc..866a542211 100755 --- a/tools/docker_build.sh +++ b/tools/docker_build.sh @@ -32,7 +32,7 @@ main () { openpype_version="$(python3 <<< ${version_command})" echo -e "${BIGreen}>>>${RST} Running docker build ..." - docker build -t pypeclub/openpype:$openpype_version . + docker build --pull --no-cache -t pypeclub/openpype:$openpype_version . if [ $? -ne 0 ] ; then echo -e "${BIRed}!!!${RST} Docker build failed." return 1 diff --git a/tools/fetch_thirdparty_libs.sh b/tools/fetch_thirdparty_libs.sh index 3875541d57..31f109ba68 100755 --- a/tools/fetch_thirdparty_libs.sh +++ b/tools/fetch_thirdparty_libs.sh @@ -5,13 +5,20 @@ art () { cat <<-EOF - ____________ - /\\ ___ \\ - \\ \\ \\/_\\ \\ - \\ \\ _____/ ______ ___ ___ ___ - \\ \\ \\___/ /\\ \\ \\ \\\\ \\\\ \\ - \\ \\____\\ \\ \\_____\\ \\__\\\\__\\\\__\\ - \\/____/ \\/_____/ . PYPE Club . + + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · EOF } @@ -51,53 +58,6 @@ BICyan='\033[1;96m' # Cyan BIWhite='\033[1;97m' # White -############################################################################## -# Detect required version of python -# Globals: -# colors -# PYTHON -# Arguments: -# None -# Returns: -# None -############################################################################### -detect_python () { - echo -e "${BIGreen}>>>${RST} Using python \c" - local version_command="import sys;print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))" - local python_version="$(python3 <<< ${version_command})" - oIFS="$IFS" - IFS=. - set -- $python_version - IFS="$oIFS" - if [ "$1" -ge "3" ] && [ "$2" -ge "6" ] ; then - if [ "$2" -gt "7" ] ; then - echo -e "${BIWhite}[${RST} ${BIRed}$1.$2 ${BIWhite}]${RST} - ${BIRed}FAILED${RST} ${BIYellow}Version is new and unsupported, use${RST} ${BIPurple}3.7.x${RST}"; return 1; - else - echo -e "${BIWhite}[${RST} ${BIGreen}$1.$2${RST} ${BIWhite}]${RST}" - fi - PYTHON="python3" - else - command -v python3 >/dev/null 2>&1 || { echo -e "${BIRed}$1.$2$ - ${BIRed}FAILED${RST} ${BIYellow}Version is old and unsupported${RST}"; return 1; } - fi -} - -############################################################################## -# Clean pyc files in specified directory -# Globals: -# None -# Arguments: -# Optional path to clean -# Returns: -# None -############################################################################### -clean_pyc () { - local path - path=$pype_root - echo -e "${BIGreen}>>>${RST} Cleaning pyc at [ ${BIWhite}$path${RST} ] ... \c" - find "$path" -regex '^.*\(__pycache__\|\.py[co]\)$' -delete - echo -e "${BIGreen}DONE${RST}" -} - ############################################################################## # Return absolute path # Globals: @@ -140,7 +100,7 @@ main () { pushd "$openpype_root" > /dev/null || return > /dev/null echo -e "${BIGreen}>>>${RST} Running Pype tool ..." - poetry run python3 "$openpype_root/tools/fetch_thirdparty_libs.py" + poetry run python "$openpype_root/tools/fetch_thirdparty_libs.py" } main \ No newline at end of file From 84e47fae0ea0983ebfb6f0ca414e480f53d2b552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Tue, 11 May 2021 15:35:43 +0200 Subject: [PATCH 16/53] updated linux oiio tgz hash --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b5edf8a3d9..3c62ee739c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ hash = "fd2e00278e01e85dcee7b4a6969d1a16f13016ec16700fb0366dbb1b1f3c37ad" [openpype.thirdparty.oiio.linux] url = "https://distribute.openpype.io/thirdparty/oiio_tools-2.2.12-linux.tgz" -hash = "53dea3e5b1acbe899268de39dd43685acd702f0f187ff37f65aa6349d22676a3" +hash = "de63a8bf7f6c45ff59ecafeba13123f710c2cbc1783ec9e0b938e980d4f5c37f" [openpype.thirdparty.oiio.darwin] url = "https://distribute.openpype.io/thirdparty/oiio-2.2.0-darwin.tgz" From 0b47e921af0c11317d0dbcc8f8b6420199db8285 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 May 2021 12:06:10 +0200 Subject: [PATCH 17/53] SyncServer - fixes for overrode settings --- .../modules/sync_server/providers/gdrive.py | 8 +- openpype/modules/sync_server/sync_server.py | 2 +- .../modules/sync_server/sync_server_module.py | 99 +++++++++++++------ 3 files changed, 72 insertions(+), 37 deletions(-) diff --git a/openpype/modules/sync_server/providers/gdrive.py b/openpype/modules/sync_server/providers/gdrive.py index e79927590b..5578a130b4 100644 --- a/openpype/modules/sync_server/providers/gdrive.py +++ b/openpype/modules/sync_server/providers/gdrive.py @@ -107,10 +107,10 @@ class GDriveHandler(AbstractProvider): """ editable = { # credentials could be override on Project or User level - 'credential_url': {'scope': [EditableScopes.PROJECT, - EditableScopes.LOCAL], - 'label': "Credentials url", - 'type': 'text'}, + 'credentials_url': {'scope': [EditableScopes.PROJECT, + EditableScopes.LOCAL], + 'label': "Credentials url", + 'type': 'text'}, # roots could be override only on Project leve, User cannot 'root': {'scope': [EditableScopes.PROJECT], 'label': "Roots", diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index b89eeebf19..262986fb12 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -213,7 +213,7 @@ def _get_configured_sites_from_setting(module, project_name, project_setting): project_name, site_name, presets=config) - initiated_handlers[(config["provider"], site_name)] = \ + initiated_handlers[(provider, site_name)] = \ handler if handler.is_active(): diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 1e12db84a1..6e54025cdf 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -2,6 +2,7 @@ import os from bson.objectid import ObjectId from datetime import datetime import threading +import platform from avalon.api import AvalonMongoDB @@ -12,6 +13,9 @@ from openpype.api import ( get_system_settings, get_local_site_id) from openpype.lib import PypeLogger +from openpype.settings.lib import ( + get_default_project_settings, + get_default_anatomy_settings) from .providers.local_drive import LocalDriveHandler from .providers import lib @@ -410,15 +414,21 @@ class SyncServerModule(PypeModule, ITrayModule): } """ editable = {} - for project in self.connection.projects(): - project_name = project["name"] + applicable_projects = list(self.connection.projects()) + applicable_projects.append(None) + for project in applicable_projects: + project_name = None + if project: + project_name = project["name"] + items = self.get_configurable_items_for_project(project_name, scope) editable.update(items) return editable - def get_configurable_items_for_project(self, project_name, scope=None): + def get_configurable_items_for_project(self, project_name=None, + scope=None): """ Returns list of items that could be configurable for specific 'project_name' @@ -435,10 +445,9 @@ class SyncServerModule(PypeModule, ITrayModule): } } """ - sites = set(self.get_active_sites(project_name)) | \ - set(self.get_remote_sites(project_name)) + sites = self.get_all_sites() editable = {} - for site_name in sites: + for site_name in sites.keys(): items = self.get_configurable_items_for_site(project_name, site_name, scope) @@ -446,7 +455,8 @@ class SyncServerModule(PypeModule, ITrayModule): return editable - def get_configurable_items_for_site(self, project_name, site_name, + def get_configurable_items_for_site(self, project_name=None, + site_name=None, scope=None): """ Returns list of items that could be configurable. @@ -465,26 +475,31 @@ class SyncServerModule(PypeModule, ITrayModule): } """ provider_name = self.get_provider_for_site(site=site_name) - items = lib.factory.get_provider_configurable_items(provider_name, - scope) + items = lib.factory.get_provider_configurable_items(provider_name) if not scope: - return {project_name: {site_name: items}} + return {site_name: items} - editable = {} - sync_s = self.get_sync_project_setting(project_name, True) + editable = [] + if project_name: + sync_s = self.get_sync_project_setting(project_name, + exclude_locals=True) + else: + sync_s = get_default_project_settings(exclude_locals=True) + sync_s = sync_s["global"]["sync_server"] + sync_s["sites"].update(self._get_default_site_configs()) for scope in set([scope]): for key, properties in items.items(): if scope in properties['scope']: val = sync_s.get("sites", {}).get(site_name, {}).get(key) - editable = { + editable.append({ "key": key, "value": val, - "label": properties.get("label"), - "type": properties.get("type"), - } + "label": properties["label"], + "type": properties["type"], + }) - return {project_name: {site_name: editable}} + return {site_name: editable} def reset_timer(self): """ @@ -663,22 +678,17 @@ class SyncServerModule(PypeModule, ITrayModule): """ sync_project_settings = {} - # sites are now configured system wide - sys_sett = get_system_settings() - sync_sett = sys_sett["modules"].get("sync_server") - system_sites = {} - for site, detail in sync_sett.get("sites", {}).items(): - system_sites[site] = detail + system_sites = self.get_all_sites() for collection in self.connection.database.collection_names(False): - sync_settings = self._parse_sync_settings_from_settings( + sites = dict(system_sites) # get all configured sites + proj_settings = self._parse_sync_settings_from_settings( get_project_settings(collection, exclude_locals=exclude_locals)) + sites.update(proj_settings["sites"]) # apply project overrides + proj_settings["sites"] = sites - default_sites = self._get_default_site_configs() - sync_settings['sites'].update(default_sites) - sync_settings['sites'].update(system_sites) - sync_project_settings[collection] = sync_settings + sync_project_settings[collection] = proj_settings if not sync_project_settings: log.info("No enabled and configured projects for sync.") @@ -699,7 +709,7 @@ class SyncServerModule(PypeModule, ITrayModule): # self.log.debug("project preset {}".format(self.presets)) if not self.sync_project_settings or \ not self.sync_project_settings.get(project_name): - self.set_sync_project_settings(project_name, exclude_locals) + self.set_sync_project_settings(exclude_locals) return self.sync_project_settings.get(project_name) @@ -713,11 +723,36 @@ class SyncServerModule(PypeModule, ITrayModule): """ Returns skeleton settings for 'studio' and user's local site """ - default_config = {'provider': 'local_drive'} - all_sites = {self.DEFAULT_SITE: default_config, - get_local_site_id(): default_config} + anatomy_sett = get_default_anatomy_settings() + roots = {} + for root, config in anatomy_sett["roots"].items(): + roots[root] = config[platform.system().lower()] + studio_config = { + 'provider': 'local_drive', + "root": roots + } + all_sites = {self.DEFAULT_SITE: studio_config, + get_local_site_id(): {'provider': 'local_drive'}} return all_sites + def get_all_sites(self): + """ + Returns (dict) with all sites configured system wide. + + Returns: + (dict): {'studio': {'provider':'local_drive'...}, + 'MY_LOCAL': {'provider':....}} + """ + sys_sett = get_system_settings() + sync_sett = sys_sett["modules"].get("sync_server") + system_sites = {} + for site, detail in sync_sett.get("sites", {}).items(): + system_sites[site] = detail + + system_sites.update(self._get_default_site_configs()) + + return system_sites + def get_provider_for_site(self, project_name=None, site=None): """ Return provider name for site (unique name across all projects. From 06a341d276aa1b2947d9f72e8a06965436c10965 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 May 2021 12:39:53 +0200 Subject: [PATCH 18/53] SyncServer - fixed return types, added documentation --- .../modules/sync_server/sync_server_module.py | 94 +++++++++++-------- 1 file changed, 53 insertions(+), 41 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 6e54025cdf..c06c7a0f15 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -20,7 +20,7 @@ from openpype.settings.lib import ( from .providers.local_drive import LocalDriveHandler from .providers import lib -from .utils import time_function, SyncStatus +from .utils import time_function, SyncStatus, EditableScopes log = PypeLogger().get_logger("SyncServer") @@ -396,21 +396,28 @@ class SyncServerModule(PypeModule, ITrayModule): return remote_site - def get_configurable_items(self, scope=None): + def get_configurable_items(self, scope=[EditableScopes.LOCAL]): """ Returns list of items that could be configurable for all projects. Could be filtered by 'scope' argument (list) Args: - scope (list of utils.EditableScope) (optional) + scope (list of utils.EditableScope) Returns: - (dict of dict) - {projectA: { - siteA : { - key:"root", label:"root", value:"valueFromSettings" - } + (dict of list of dict) + { + siteA : [ + { + key:"root", label:"root", + "value":"valueFromSettings", "type": "text" + }, + { + key:"credentials_url", label:"Credentials url", + ... + } + ] } """ editable = {} @@ -428,21 +435,26 @@ class SyncServerModule(PypeModule, ITrayModule): return editable def get_configurable_items_for_project(self, project_name=None, - scope=None): + scope=[EditableScopes.LOCAL]): """ Returns list of items that could be configurable for specific 'project_name' Args: - project_name (str) - scope (list of utils.EditableScope) (optional) + project_name (str) - None > default project, + scope (list of utils.EditableScope) + (optional, None is all scopes, default is LOCAL) Returns: - (dict of dict) - {projectA: { - siteA : { - key:"root", label:"root", value:"valueFromSettings" - } + (dict of list of dict) + { + siteA : [ + { + key:"root", label:"root", + "value":"valueFromSettings", "type": "text" + }, + studio: {...} + ] } """ sites = self.get_all_sites() @@ -451,36 +463,34 @@ class SyncServerModule(PypeModule, ITrayModule): items = self.get_configurable_items_for_site(project_name, site_name, scope) - editable.update(items) + editable[site_name] = items return editable def get_configurable_items_for_site(self, project_name=None, site_name=None, - scope=None): + scope=[EditableScopes.LOCAL]): """ Returns list of items that could be configurable. Args: - project_name (str) + project_name (str) - None > default project site_name (str) - scope (list of utils.EditableScope) (optional) + scope (list of utils.EditableScope) + (optional, None is all scopes) Returns: - (dict of dict) - {projectA: { - siteA : { - key:"root", label:"root", value:"valueFromSettings" - } - } + (list) + [ + { + key:"root", label:"root", + "value":"valueFromSettings", "type": "text" + }, ... + ] """ provider_name = self.get_provider_for_site(site=site_name) items = lib.factory.get_provider_configurable_items(provider_name) - if not scope: - return {site_name: items} - - editable = [] if project_name: sync_s = self.get_sync_project_setting(project_name, exclude_locals=True) @@ -488,18 +498,20 @@ class SyncServerModule(PypeModule, ITrayModule): sync_s = get_default_project_settings(exclude_locals=True) sync_s = sync_s["global"]["sync_server"] sync_s["sites"].update(self._get_default_site_configs()) - for scope in set([scope]): - for key, properties in items.items(): - if scope in properties['scope']: - val = sync_s.get("sites", {}).get(site_name, {}).get(key) - editable.append({ - "key": key, - "value": val, - "label": properties["label"], - "type": properties["type"], - }) - return {site_name: editable} + editable = [] + scope = set([scope]) + for key, properties in items.items(): + if scope is None or scope.intersection(set(properties["scope"])): + val = sync_s.get("sites", {}).get(site_name, {}).get(key) + editable.append({ + "key": key, + "value": val, + "label": properties["label"], + "type": properties["type"], + }) + + return editable def reset_timer(self): """ From 5461ee461ed5e67bb34586462e0f1b69d59e9ef5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 May 2021 12:54:47 +0200 Subject: [PATCH 19/53] SyncServer - fixed documentation --- openpype/modules/sync_server/sync_server_module.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index c06c7a0f15..b77d19a9c3 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -411,11 +411,11 @@ class SyncServerModule(PypeModule, ITrayModule): siteA : [ { key:"root", label:"root", - "value":"valueFromSettings", "type": "text" + "value":"{'work': 'c:/projects'}", "type": "dict" }, { key:"credentials_url", label:"Credentials url", - ... + "value":"'c:/projects/cred.json'", "type": "text" } ] } @@ -451,9 +451,12 @@ class SyncServerModule(PypeModule, ITrayModule): siteA : [ { key:"root", label:"root", - "value":"valueFromSettings", "type": "text" + "value":"{'work': 'c:/projects'}", "type": "dict" }, - studio: {...} + { + key:"credentials_url", label:"Credentials url", + "value":"'c:/projects/cred.json'", "type": "text" + } ] } """ @@ -484,7 +487,7 @@ class SyncServerModule(PypeModule, ITrayModule): [ { key:"root", label:"root", - "value":"valueFromSettings", "type": "text" + "value":"{'work': 'c:/projects'}", "type": "dict" }, ... ] """ From 895d7ac18a58aa51f6b9534de4db265ceb8b203e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 May 2021 13:04:51 +0200 Subject: [PATCH 20/53] SyncServer - added explicit wrappers Changed back method signature --- .../modules/sync_server/sync_server_module.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index b77d19a9c3..bcd2271eaa 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -396,7 +396,7 @@ class SyncServerModule(PypeModule, ITrayModule): return remote_site - def get_configurable_items(self, scope=[EditableScopes.LOCAL]): + def get_configurable_items(self, scope=None): """ Returns list of items that could be configurable for all projects. @@ -434,8 +434,12 @@ class SyncServerModule(PypeModule, ITrayModule): return editable + def get_local_settings_schema_for_project(self, project_name): + """Wrapper for Local settings""" + return self.get_configurable_items(project_name, EditableScopes.LOCAL) + def get_configurable_items_for_project(self, project_name=None, - scope=[EditableScopes.LOCAL]): + scope=None): """ Returns list of items that could be configurable for specific 'project_name' @@ -470,9 +474,15 @@ class SyncServerModule(PypeModule, ITrayModule): return editable + def get_local_settings_schema_for_site(self, project_name, site_name): + """Wrapper for Local settings""" + return self.get_configurable_items(project_name, + site_name, + EditableScopes.LOCAL) + def get_configurable_items_for_site(self, project_name=None, site_name=None, - scope=[EditableScopes.LOCAL]): + scope=None): """ Returns list of items that could be configurable. @@ -503,7 +513,9 @@ class SyncServerModule(PypeModule, ITrayModule): sync_s["sites"].update(self._get_default_site_configs()) editable = [] - scope = set([scope]) + if type(scope) is not list: + scope = [scope] + scope = set(scope) for key, properties in items.items(): if scope is None or scope.intersection(set(properties["scope"])): val = sync_s.get("sites", {}).get(site_name, {}).get(key) From 1d24e2fc32d9b53453bf90b2116ad9726888c783 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 May 2021 15:42:05 +0200 Subject: [PATCH 21/53] SyncServer - modify which sites return when disabled --- .../modules/sync_server/sync_server_module.py | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index bcd2271eaa..6a97b66e63 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -464,7 +464,7 @@ class SyncServerModule(PypeModule, ITrayModule): ] } """ - sites = self.get_all_sites() + sites = self.get_all_sites(project_name) editable = {} for site_name in sites.keys(): items = self.get_configurable_items_for_site(project_name, @@ -510,7 +510,8 @@ class SyncServerModule(PypeModule, ITrayModule): else: sync_s = get_default_project_settings(exclude_locals=True) sync_s = sync_s["global"]["sync_server"] - sync_s["sites"].update(self._get_default_site_configs()) + sync_s["sites"].update( + self._get_default_site_configs(self.enabled)) editable = [] if type(scope) is not list: @@ -746,7 +747,7 @@ class SyncServerModule(PypeModule, ITrayModule): return sync_settings - def _get_default_site_configs(self): + def _get_default_site_configs(self, sync_enabled=True): """ Returns skeleton settings for 'studio' and user's local site """ @@ -758,25 +759,36 @@ class SyncServerModule(PypeModule, ITrayModule): 'provider': 'local_drive', "root": roots } - all_sites = {self.DEFAULT_SITE: studio_config, - get_local_site_id(): {'provider': 'local_drive'}} + all_sites = {self.DEFAULT_SITE: studio_config} + if sync_enabled: + all_sites[get_local_site_id()] = {'provider': 'local_drive'} return all_sites - def get_all_sites(self): + def get_all_sites(self, project_name=None): """ Returns (dict) with all sites configured system wide. + Args: + project_name (str)(optional): if present, check if not disabled + Returns: (dict): {'studio': {'provider':'local_drive'...}, 'MY_LOCAL': {'provider':....}} """ sys_sett = get_system_settings() sync_sett = sys_sett["modules"].get("sync_server") - system_sites = {} - for site, detail in sync_sett.get("sites", {}).items(): - system_sites[site] = detail - system_sites.update(self._get_default_site_configs()) + project_enabled = True + if project_name: + project_enabled = project_name in self.get_enabled_projects() + sync_enabled = sync_sett["enabled"] and project_enabled + + system_sites = {} + if sync_enabled: + for site, detail in sync_sett.get("sites", {}).items(): + system_sites[site] = detail + + system_sites.update(self._get_default_site_configs(sync_enabled)) return system_sites From 19c4b1e41dc4a21b2d00288b56f257d7c8410aeb Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 12 May 2021 15:43:50 +0200 Subject: [PATCH 22/53] copy libs from centos 7 to build --- Dockerfile | 5 ++++- tools/docker_build.sh | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6a4cd3599d..2d8ed27b15 100644 --- a/Dockerfile +++ b/Dockerfile @@ -73,7 +73,10 @@ RUN source $HOME/.bashrc \ && ./tools/fetch_thirdparty_libs.sh RUN source $HOME/.bashrc \ - && bash ./tools/build.sh + && 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 diff --git a/tools/docker_build.sh b/tools/docker_build.sh index 866a542211..7600fe044b 100755 --- a/tools/docker_build.sh +++ b/tools/docker_build.sh @@ -51,6 +51,11 @@ main () { echo -e "${BIRed}!!!${RST} Copying failed." return 1 fi + + echo -e "${BIGreen}>>>${RST} Fixing user ownership ..." + username="$(logname)" + chown -R $username ./build + echo -e "${BIGreen}>>>${RST} All done, you can delete container:" echo -e "${BIYellow}$id${RST}" } From c4d5baae6588630a7424d22c526bdb10d594b6a7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 May 2021 15:52:16 +0200 Subject: [PATCH 23/53] SyncServer - changed output format, added children list --- .../modules/sync_server/sync_server_module.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 6a97b66e63..f834caeb38 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -520,12 +520,29 @@ class SyncServerModule(PypeModule, ITrayModule): for key, properties in items.items(): if scope is None or scope.intersection(set(properties["scope"])): val = sync_s.get("sites", {}).get(site_name, {}).get(key) - editable.append({ + + children = [] + if properties["type"] == "dict": + if val: + for val_key, val_val in val.items(): + child = { + "type": "text", + "key": val_key, + "value": val_val + } + children.append(child) + + item = { "key": key, - "value": val, "label": properties["label"], - "type": properties["type"], - }) + "type": properties["type"] + } + if properties["type"] == "dict": + item["children"] = children + else: + item["value"] = val + + editable.append(item) return editable From fca039884aad17e204d8c863d59edf78fe5a7a55 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 May 2021 15:54:19 +0200 Subject: [PATCH 24/53] SyncServer - local_drive sites (studio, local) only configurable in Local Settings In Project settings only configured via Anatomy --- openpype/modules/sync_server/providers/local_drive.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/modules/sync_server/providers/local_drive.py b/openpype/modules/sync_server/providers/local_drive.py index 2d37d0e1c4..3b3e699d00 100644 --- a/openpype/modules/sync_server/providers/local_drive.py +++ b/openpype/modules/sync_server/providers/local_drive.py @@ -38,8 +38,7 @@ class LocalDriveHandler(AbstractProvider): (dict) """ editable = { - 'root': {'scope': [EditableScopes.PROJECT, - EditableScopes.LOCAL], + 'root': {'scope': [EditableScopes.LOCAL], 'label': "Roots", 'type': 'dict'} } From 97c2a47024236629ba59046063e5ad154a3ffa39 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 May 2021 15:54:45 +0200 Subject: [PATCH 25/53] SyncServer - credentials_url is multiplatform path --- .../schemas/projects_schema/schema_project_syncserver.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_syncserver.json b/openpype/settings/entities/schemas/projects_schema/schema_project_syncserver.json index bb5ebea45a..9428ce2db0 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_syncserver.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_syncserver.json @@ -50,9 +50,10 @@ "type": "dict", "children": [ { - "type": "text", + "type": "path", "key": "credentials_url", - "label": "Credentials url" + "label": "Credentials url", + "multiplatform": true }, { "type": "dict-modifiable", From bb8452f44eca71c87bb6b24e8fa766bd15cfa801 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 May 2021 15:57:13 +0200 Subject: [PATCH 26/53] SyncServer - add namespace for gdrive Namespace points Local Settings where to overwrite value. Namespace values should be unique paths, placeholders could be used. --- openpype/modules/sync_server/providers/gdrive.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/modules/sync_server/providers/gdrive.py b/openpype/modules/sync_server/providers/gdrive.py index 5578a130b4..851c190094 100644 --- a/openpype/modules/sync_server/providers/gdrive.py +++ b/openpype/modules/sync_server/providers/gdrive.py @@ -107,10 +107,12 @@ class GDriveHandler(AbstractProvider): """ editable = { # credentials could be override on Project or User level - 'credentials_url': {'scope': [EditableScopes.PROJECT, - EditableScopes.LOCAL], - 'label': "Credentials url", - 'type': 'text'}, + 'credentials_url': { + 'scope': [EditableScopes.PROJECT, + EditableScopes.LOCAL], + 'label': "Credentials url", + 'type': 'text', + 'namespace': '{project_setting}/global/sync_server/sites'}, # roots could be override only on Project leve, User cannot 'root': {'scope': [EditableScopes.PROJECT], 'label': "Roots", From abfb7589227b40b65ed791253c9fa298a7676721 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 May 2021 16:22:33 +0200 Subject: [PATCH 27/53] SyncServer - limit return only configured sites to Local Settings --- openpype/modules/sync_server/sync_server_module.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index f834caeb38..29db74555a 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -464,9 +464,18 @@ class SyncServerModule(PypeModule, ITrayModule): ] } """ + allowed_sites = set() sites = self.get_all_sites(project_name) + if project_name: + # Local Settings can select only from allowed sites for project + allowed_sites.update(set(self.get_active_sites(project_name))) + allowed_sites.update(set(self.get_remote_sites(project_name))) + editable = {} for site_name in sites.keys(): + if allowed_sites and site_name not in allowed_sites: + continue + items = self.get_configurable_items_for_site(project_name, site_name, scope) From 798f67ff64ac7886540d3ba4407eb3319c21e514 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 May 2021 16:41:29 +0200 Subject: [PATCH 28/53] SyncServer - added namespace field --- .../modules/sync_server/sync_server_module.py | 54 +++++++++++++------ 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 29db74555a..08810dbad1 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -411,11 +411,19 @@ class SyncServerModule(PypeModule, ITrayModule): siteA : [ { key:"root", label:"root", - "value":"{'work': 'c:/projects'}", "type": "dict" + "value":"{'work': 'c:/projects'}", + "type": "dict", + "children":[ + { "key": "work", + "type": "text", + "value": "c:/projects"} + ] }, { key:"credentials_url", label:"Credentials url", - "value":"'c:/projects/cred.json'", "type": "text" + "value":"'c:/projects/cred.json'", "type": "text", + "namespace": "{project_setting}/global/sync_server/ + sites" } ] } @@ -451,18 +459,25 @@ class SyncServerModule(PypeModule, ITrayModule): Returns: (dict of list of dict) - { - siteA : [ - { - key:"root", label:"root", - "value":"{'work': 'c:/projects'}", "type": "dict" - }, - { - key:"credentials_url", label:"Credentials url", - "value":"'c:/projects/cred.json'", "type": "text" - } - ] - } + { + siteA : [ + { + key:"root", label:"root", + "type": "dict", + "children":[ + { "key": "work", + "type": "text", + "value": "c:/projects"} + ] + }, + { + key:"credentials_url", label:"Credentials url", + "value":"'c:/projects/cred.json'", "type": "text", + "namespace": "{project_setting}/global/sync_server/ + sites" + } + ] + } """ allowed_sites = set() sites = self.get_all_sites(project_name) @@ -505,8 +520,12 @@ class SyncServerModule(PypeModule, ITrayModule): (list) [ { - key:"root", label:"root", - "value":"{'work': 'c:/projects'}", "type": "dict" + key:"root", label:"root", type:"dict", + "children":[ + { "key": "work", + "type": "text", + "value": "c:/projects"} + ] }, ... ] """ @@ -551,6 +570,9 @@ class SyncServerModule(PypeModule, ITrayModule): else: item["value"] = val + if properties.get("namespace"): + item["namespace"] = properties.get("namespace") + editable.append(item) return editable From eeb9722652e238725dd6f905a7b10ccb7418a574 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 May 2021 17:07:12 +0200 Subject: [PATCH 29/53] SyncServer - fixed resolving of credentials_url Moved root config creation before its totally necessary --- .../modules/sync_server/providers/gdrive.py | 84 ++++++++++--------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/openpype/modules/sync_server/providers/gdrive.py b/openpype/modules/sync_server/providers/gdrive.py index 851c190094..d3ae0477e2 100644 --- a/openpype/modules/sync_server/providers/gdrive.py +++ b/openpype/modules/sync_server/providers/gdrive.py @@ -3,6 +3,7 @@ import os.path import time import sys from setuptools.extern import six +import platform from openpype.api import Logger from openpype.api import get_system_settings @@ -65,6 +66,7 @@ class GDriveHandler(AbstractProvider): self.project_name = project_name self.site_name = site_name self.service = None + self.root = None self.presets = presets if not self.presets: @@ -72,18 +74,13 @@ class GDriveHandler(AbstractProvider): format(site_name)) return - if not os.path.exists(self.presets.get("credentials_url", "")): + cred_path = self.presets.get("credentials_url", {}).\ + get(platform.system().lower()) or '' + if not os.path.exists(cred_path): log.info("Sync Server: No credentials for Gdrive provider! ") return - self.service = self._get_gd_service() - try: - self.root = self._prepare_root_info() - except errors.HttpError: - log.warning("HttpError in sync loop, " - "trying next loop", - exc_info=True) - raise ResumableError + self.service = self._get_gd_service(cred_path) self._tree = tree self.active = True @@ -575,7 +572,7 @@ class GDriveHandler(AbstractProvider): return return provider_presets - def _get_gd_service(self): + def _get_gd_service(self, credentials_path): """ Authorize client with 'credentials.json', uses service account. Service account needs to have target folder shared with. @@ -585,7 +582,7 @@ class GDriveHandler(AbstractProvider): None """ creds = service_account.Credentials.from_service_account_file( - self.presets["credentials_url"], + credentials_path, scopes=SCOPES) service = build('drive', 'v3', credentials=creds, cache_discovery=False) @@ -600,39 +597,47 @@ class GDriveHandler(AbstractProvider): Returns: (dicts) of dicts where root folders are keys + throws ResumableError in case of errors.HttpError """ roots = {} config_roots = self.get_roots_config() - for path in config_roots.values(): - if self.MY_DRIVE_STR in path: - roots[self.MY_DRIVE_STR] = self.service.files()\ - .get(fileId='root').execute() - else: - shared_drives = [] - page_token = None + try: + for path in config_roots.values(): + if self.MY_DRIVE_STR in path: + roots[self.MY_DRIVE_STR] = self.service.files()\ + .get(fileId='root')\ + .execute() + else: + shared_drives = [] + page_token = None - while True: - response = self.service.drives().list( - pageSize=100, - pageToken=page_token).execute() - shared_drives.extend(response.get('drives', [])) - page_token = response.get('nextPageToken', None) - if page_token is None: - break + while True: + response = self.service.drives().list( + pageSize=100, + pageToken=page_token).execute() + shared_drives.extend(response.get('drives', [])) + page_token = response.get('nextPageToken', None) + if page_token is None: + break - folders = path.split('/') - if len(folders) < 2: - raise ValueError("Wrong root folder definition {}". - format(path)) + folders = path.split('/') + if len(folders) < 2: + raise ValueError("Wrong root folder definition {}". + format(path)) - for shared_drive in shared_drives: - if folders[1] in shared_drive["name"]: - roots[shared_drive["name"]] = { - "name": shared_drive["name"], - "id": shared_drive["id"]} - if self.MY_DRIVE_STR not in roots: # add My Drive always - roots[self.MY_DRIVE_STR] = self.service.files() \ - .get(fileId='root').execute() + for shared_drive in shared_drives: + if folders[1] in shared_drive["name"]: + roots[shared_drive["name"]] = { + "name": shared_drive["name"], + "id": shared_drive["id"]} + if self.MY_DRIVE_STR not in roots: # add My Drive always + roots[self.MY_DRIVE_STR] = self.service.files() \ + .get(fileId='root').execute() + except errors.HttpError: + log.warning("HttpError in sync loop, " + "trying next loop", + exc_info=True) + raise ResumableError return roots @@ -653,6 +658,9 @@ class GDriveHandler(AbstractProvider): (dictionary) path as a key, folder id as a value """ log.debug("build_tree len {}".format(len(folders))) + if not self.root: # build only when necessary, could be expensive + self.root = self._prepare_root_info() + root_ids = [] default_root_id = None tree = {} From 4da285cf8f7f21ea411a9343ba01466189384582 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 May 2021 17:30:59 +0200 Subject: [PATCH 30/53] SyncServer - fix multiplatform properties --- .../modules/sync_server/providers/gdrive.py | 5 +++- .../modules/sync_server/sync_server_module.py | 27 ++++++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/openpype/modules/sync_server/providers/gdrive.py b/openpype/modules/sync_server/providers/gdrive.py index d3ae0477e2..eaa2f5643b 100644 --- a/openpype/modules/sync_server/providers/gdrive.py +++ b/openpype/modules/sync_server/providers/gdrive.py @@ -102,6 +102,8 @@ class GDriveHandler(AbstractProvider): Returns: (dict) """ + # {platform} tells that value is multiplatform and only specific OS + # should be returned editable = { # credentials could be override on Project or User level 'credentials_url': { @@ -109,7 +111,8 @@ class GDriveHandler(AbstractProvider): EditableScopes.LOCAL], 'label': "Credentials url", 'type': 'text', - 'namespace': '{project_setting}/global/sync_server/sites'}, + 'namespace': '{project_setting}/global/sync_server/sites/{site}/{platform}' # noqa: E501 + }, # roots could be override only on Project leve, User cannot 'root': {'scope': [EditableScopes.PROJECT], 'label': "Roots", diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 08810dbad1..69f9131b6f 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -485,6 +485,9 @@ class SyncServerModule(PypeModule, ITrayModule): # Local Settings can select only from allowed sites for project allowed_sites.update(set(self.get_active_sites(project_name))) allowed_sites.update(set(self.get_remote_sites(project_name))) + # Settings allow use of 'local' site, user's site is not 'local' + if 'local' in allowed_sites: + allowed_sites.add(get_local_site_id()) editable = {} for site_name in sites.keys(): @@ -549,6 +552,22 @@ class SyncServerModule(PypeModule, ITrayModule): if scope is None or scope.intersection(set(properties["scope"])): val = sync_s.get("sites", {}).get(site_name, {}).get(key) + item = { + "key": key, + "label": properties["label"], + "type": properties["type"] + } + + if properties.get("namespace"): + item["namespace"] = properties.get("namespace") + if "platform" in item["namespace"]: + try: + if val: + val = val[platform.system().lower()] + except KeyError: + st = "{}'s field value {} should be".format(key, val) # noqa: E501 + log.error(st + " multiplatform dict") + children = [] if properties["type"] == "dict": if val: @@ -560,18 +579,12 @@ class SyncServerModule(PypeModule, ITrayModule): } children.append(child) - item = { - "key": key, - "label": properties["label"], - "type": properties["type"] - } if properties["type"] == "dict": item["children"] = children else: item["value"] = val - if properties.get("namespace"): - item["namespace"] = properties.get("namespace") + editable.append(item) From 10c0bd279b068b55c41a4a3a099363984262ca14 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 May 2021 17:46:46 +0200 Subject: [PATCH 31/53] SyncServer - fixed broken studio <> remote use case User should only upload/download when active or remote site is his. --- .../modules/sync_server/sync_server_module.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 69f9131b6f..0c9a12fcbf 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -994,6 +994,15 @@ class SyncServerModule(PypeModule, ITrayModule): Always is comparing local record, eg. site with 'name' == self.presets[PROJECT_NAME]['config']["active_site"] + This leads to trigger actual upload or download, there is + a use case 'studio' <> 'remote' where user should publish + to 'studio', see progress in Tray GUI, but do not do + physical upload/download + (as multiple user would be doing that). + + Do physical U/D only when any of the sites is user's local, in that + case only user has the data and must U/D. + Args: file (dictionary): of file from representation in Mongo local_site (string): - local side of compare (usually 'studio') @@ -1003,8 +1012,12 @@ class SyncServerModule(PypeModule, ITrayModule): (string) - one of SyncStatus """ sites = file.get("sites") or [] - # if isinstance(sites, list): # temporary, old format of 'sites' - # return SyncStatus.DO_NOTHING + + if get_local_site_id() not in (local_site, remote_site): + # don't do upload/download for studio sites + log.debug("No local site {} - {}".format(local_site, remote_site)) + return SyncStatus.DO_NOTHING + _, remote_rec = self._get_site_rec(sites, remote_site) or {} if remote_rec: # sync remote target created_dt = remote_rec.get("created_dt") From adc1f570b1a19693c24a5f5c25903b3ce44b18e4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 May 2021 19:24:19 +0200 Subject: [PATCH 32/53] SyncServer - fix broken provider access --- openpype/modules/sync_server/sync_server.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 262986fb12..638a4a367f 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -298,13 +298,14 @@ class SyncServerThread(threading.Thread): processed_file_path = set() site_preset = preset.get('sites')[remote_site] - remote_provider = site_preset['provider'] + remote_provider = \ + self.module.get_provider_for_site(site=remote_site) handler = lib.factory.get_provider(remote_provider, collection, remote_site, presets=site_preset) limit = lib.factory.get_provider_batch_limit( - site_preset['provider']) + remote_provider) # first call to get_provider could be expensive, its # building folder tree structure in memory # call only if needed, eg. DO_UPLOAD or DO_DOWNLOAD From 0e944f121e8355f9606cad1a6c402919e0ab25b2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 May 2021 19:28:46 +0200 Subject: [PATCH 33/53] SyncServer - fix broken provider access in module --- openpype/modules/sync_server/sync_server_module.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 0c9a12fcbf..aefe8195c4 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -868,7 +868,8 @@ class SyncServerModule(PypeModule, ITrayModule): proj_settings = self.get_sync_project_setting(project_name) provider = proj_settings.get("sites", {}).get(site, {}).\ get("provider") - return provider + if provider: + return provider sys_sett = get_system_settings() sync_sett = sys_sett["modules"].get("sync_server") From 86cf197d07d89f36550a093f823d0798a0e62126 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 May 2021 20:09:05 +0200 Subject: [PATCH 34/53] SyncServer - added better error for not working credentials file --- .../modules/sync_server/providers/gdrive.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/openpype/modules/sync_server/providers/gdrive.py b/openpype/modules/sync_server/providers/gdrive.py index eaa2f5643b..65fccb3215 100644 --- a/openpype/modules/sync_server/providers/gdrive.py +++ b/openpype/modules/sync_server/providers/gdrive.py @@ -4,6 +4,7 @@ import time import sys from setuptools.extern import six import platform +from json.decoder import JSONDecodeError from openpype.api import Logger from openpype.api import get_system_settings @@ -77,7 +78,9 @@ class GDriveHandler(AbstractProvider): cred_path = self.presets.get("credentials_url", {}).\ get(platform.system().lower()) or '' if not os.path.exists(cred_path): - log.info("Sync Server: No credentials for Gdrive provider! ") + msg = "Sync Server: No credentials for gdrive provider " + \ + "for '{}' on path '{}'!".format(site_name, cred_path) + log.info(msg) return self.service = self._get_gd_service(cred_path) @@ -584,11 +587,18 @@ class GDriveHandler(AbstractProvider): Returns: None """ - creds = service_account.Credentials.from_service_account_file( - credentials_path, - scopes=SCOPES) - service = build('drive', 'v3', - credentials=creds, cache_discovery=False) + service = None + try: + creds = service_account.Credentials.from_service_account_file( + credentials_path, + scopes=SCOPES) + service = build('drive', 'v3', + credentials=creds, cache_discovery=False) + except Exception: + log.error("Connection failed, " + + "check '{}' credentials file".format(credentials_path), + exc_info=True) + return service def _prepare_root_info(self): From 4d657841f1844a3aec048a7ab56c3b66c6f7acb0 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 12 May 2021 23:30:35 +0200 Subject: [PATCH 35/53] bump version --- openpype/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/version.py b/openpype/version.py index 68f154f5eb..27186ad2bb 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.0.0-rc3" +__version__ = "3.0.0-rc4" diff --git a/pyproject.toml b/pyproject.toml index 3c62ee739c..1c3c5ad44e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.0.0-rc3" +version = "3.0.0-rc4" description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From e80488f5820fe90c9ff8630f3ebeb6fa548fcdc0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 13 May 2021 12:12:07 +0200 Subject: [PATCH 36/53] Harmony - added documentation for Project Settings --- website/docs/admin_hosts_harmony.md | 51 ++++++++++++++++++ .../assets/admin_hosts_harmony_settings.png | Bin 0 -> 39384 bytes website/sidebars.js | 3 +- 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 website/docs/admin_hosts_harmony.md create mode 100644 website/docs/assets/admin_hosts_harmony_settings.png diff --git a/website/docs/admin_hosts_harmony.md b/website/docs/admin_hosts_harmony.md new file mode 100644 index 0000000000..756ca1c27f --- /dev/null +++ b/website/docs/admin_hosts_harmony.md @@ -0,0 +1,51 @@ +--- +id: admin_hosts_harmony +title: ToonBoom Harmony Settings +sidebar_label: ToonBoom Harmony +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +## ToonBoom Harmony settings + +There is a couple of settings that could configure publishing process for **ToonBoom Harmony**. +All of them are Project based, eg. each project could have different configuration. + +Location: Settings > Project > Harmony + +![Harmony Project Settings](assets/admin_hosts_harmony_settings.png) + +## Publish plugins + +### Collect Palettes + +#### Allowed tasks + +Set regex pattern(s) only for task names when publishing of Palettes should occur. + +Use ".*" for publish Palettes for ALL tasks. + +### Validate Scene Settings + +#### Skip Frame check for Assets with + +Set regex pattern(s) to find in Asset name to skip checks of `frameEnd` value from DB. + +#### Skip Resolution Check for Tasks + +Set regex pattern(s) to find in Task name to skip resolution check against values from DB. + +#### Skip Timeline Check for Tasks + +Set regex pattern(s) to find in Task name to skip `frameStart`, `frameEnd` check against values from DB. + +### Harmony Submit to Deadline + +* `Use Published scene` - Set to True (green) when Deadline should take published scene as a source instead of uploaded local one. +* `Priority` - priority of job on farm +* `Primary Pool` - here is list of pool fetched from server you can select from. +* `Secondary Pool` +* `Frames Per Task` - number of sequence division between individual tasks (chunks) +making one job on farm. + diff --git a/website/docs/assets/admin_hosts_harmony_settings.png b/website/docs/assets/admin_hosts_harmony_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..800a64e98664aa1f947a5f770319c36c2a4dfc38 GIT binary patch literal 39384 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 Date: Thu, 13 May 2021 12:17:31 +0200 Subject: [PATCH 37/53] Harmony - cleaned up settings location Standardized to use regex when possible Removed 'General' sections as everything is in ValidateSceneSettings --- openpype/hosts/harmony/api/__init__.py | 22 +++++--- .../publish/validate_scene_settings.py | 24 +++++---- .../defaults/project_settings/harmony.json | 16 +++--- .../schema_project_harmony.json | 50 +++++++++++-------- 4 files changed, 66 insertions(+), 46 deletions(-) diff --git a/openpype/hosts/harmony/api/__init__.py b/openpype/hosts/harmony/api/__init__.py index 705ccef892..523f947be5 100644 --- a/openpype/hosts/harmony/api/__init__.py +++ b/openpype/hosts/harmony/api/__init__.py @@ -3,6 +3,7 @@ import os from pathlib import Path import logging +import re from openpype import lib from openpype.api import (get_current_project_settings) @@ -68,19 +69,28 @@ def get_asset_settings(): settings = get_current_project_settings() try: - skip_resolution_check = \ - settings["harmony"]["general"]["skip_resolution_check"] - skip_timelines_check = \ - settings["harmony"]["general"]["skip_timelines_check"] + skip_resolution_check = (settings["plugins"] + ["harmony"] + ["publish"] + ["ValidateSceneSettings"] + ["skip_resolution_check"]) + + skip_timelines_check = (settings["plugins"] + ["harmony"] + ["publish"] + ["ValidateSceneSettings"] + ["skip_timelines_check"]) except KeyError: skip_resolution_check = [] skip_timelines_check = [] - if os.getenv('AVALON_TASK') in skip_resolution_check: + if (any(re.search(pattern, os.getenv('AVALON_TASK')) + for pattern in skip_resolution_check)): scene_data.pop("resolutionWidth") scene_data.pop("resolutionHeight") - if entity_type in skip_timelines_check: + if (any(re.search(pattern, entity_type) + for pattern in skip_timelines_check)): scene_data.pop('frameStart', None) scene_data.pop('frameEnd', None) diff --git a/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py b/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py index b3e7f49268..c5cd141636 100644 --- a/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py +++ b/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py @@ -2,6 +2,7 @@ """Validate scene settings.""" import os import json +import re import pyblish.api @@ -41,10 +42,16 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): families = ["workfile"] hosts = ["harmony"] actions = [ValidateSceneSettingsRepair] + optional = True - frame_check_filter = ["_ch_", "_pr_", "_intd_", "_extd_"] - # used for skipping resolution validation for render tasks - render_check_filter = ["render", "Render"] + # skip frameEnd check if asset contains any of: + frame_check_filter = ["_ch_", "_pr_", "_intd_", "_extd_"] # regex + + # skip resolution check if Task name matches any of regex patterns + skip_resolution_check = ["render", "Render"] # regex + + # skip frameStart, frameEnd check if Task name matches any of regex patt. + skip_timelines_check = [] # regex def process(self, instance): """Plugin entry point.""" @@ -55,8 +62,9 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): expected_settings["frameEndHandle"] = expected_settings["frameEnd"] +\ expected_settings["handleEnd"] - if any(string in instance.context.data['anatomyData']['asset'] - for string in self.frame_check_filter): + asset_name = instance.context.data['anatomyData']['asset'] + if any(re.search(pattern, asset_name) + for pattern in self.frame_check_filter): expected_settings.pop("frameEnd") # handle case where ftrack uses only two decimal places @@ -66,12 +74,6 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): fps = float( "{:.2f}".format(instance.context.data.get("frameRate"))) - if any(string in instance.context.data['anatomyData']['task'] - for string in self.render_check_filter): - self.log.debug("Render task detected, resolution check skipped") - expected_settings.pop("resolutionWidth") - expected_settings.pop("resolutionHeight") - self.log.debug(expected_settings) current_settings = { diff --git a/openpype/settings/defaults/project_settings/harmony.json b/openpype/settings/defaults/project_settings/harmony.json index f5f084dd44..2131516805 100644 --- a/openpype/settings/defaults/project_settings/harmony.json +++ b/openpype/settings/defaults/project_settings/harmony.json @@ -1,13 +1,15 @@ { - "general": { - "skip_resolution_check": [], - "skip_timelines_check": [] - }, "publish": { "CollectPalettes": { - "allowed_tasks": [ - "." - ] + "allowed_tasks": [".*"] + }, + "ValidateSceneSettings": { + "enabled": true, + "optional": true, + "active": true, + "frame_check_filter": ["_ch_", "_pr_", "_intd_", "_extd_"], + "skip_resolution_check": ["render", "Render"], + "skip_timelines_check": [] }, "HarmonySubmitDeadline": { "use_published": false, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json index c4cdccff42..8b4e379691 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json @@ -5,26 +5,6 @@ "label": "Harmony", "is_file": true, "children": [ - { - "type": "dict", - "collapsible": true, - "key": "general", - "label": "General", - "children": [ - { - "type": "list", - "key": "skip_resolution_check", - "object_type": "text", - "label": "Skip Resolution Check for Tasks" - }, - { - "type": "list", - "key": "skip_timelines_check", - "object_type": "text", - "label": "Skip Timeliene Check for Tasks" - } - ] - }, { "type": "dict", "collapsible": true, @@ -45,6 +25,32 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "ValidateSceneSettings", + "label": "Validate Scene Settings", + "children": [ + { + "type": "list", + "key": "frame_check_filter", + "label": "Skip Frame check for Assets with", + "object_type": "text" + }, + { + "type": "list", + "key": "skip_resolution_check", + "object_type": "text", + "label": "Skip Resolution Check for Tasks" + }, + { + "type": "list", + "key": "skip_timelines_check", + "object_type": "text", + "label": "Skip Timeline Check for Tasks" + } + ] + }, { "type": "dict", "collapsible": true, @@ -59,7 +65,7 @@ { "type": "number", "key": "priority", - "label": "priority" + "label": "Priority" }, { "type": "text", @@ -74,7 +80,7 @@ { "type": "number", "key": "chunk_size", - "label": "Chunk Size" + "label": "Frames Per Task" } ] } From ef5dd3c543563f5396f0ac0676dd0ea954a0c450 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 13 May 2021 13:59:53 +0200 Subject: [PATCH 38/53] Harmony - moved filtering of DB setting from init to validator --- openpype/hosts/harmony/api/__init__.py | 30 ++----------------- .../publish/validate_scene_settings.py | 19 ++++++++++-- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/openpype/hosts/harmony/api/__init__.py b/openpype/hosts/harmony/api/__init__.py index 523f947be5..fd21725bd5 100644 --- a/openpype/hosts/harmony/api/__init__.py +++ b/openpype/hosts/harmony/api/__init__.py @@ -64,35 +64,9 @@ def get_asset_settings(): "handleStart": handle_start, "handleEnd": handle_end, "resolutionWidth": resolution_width, - "resolutionHeight": resolution_height + "resolutionHeight": resolution_height, + "entityType": entity_type } - settings = get_current_project_settings() - - try: - skip_resolution_check = (settings["plugins"] - ["harmony"] - ["publish"] - ["ValidateSceneSettings"] - ["skip_resolution_check"]) - - skip_timelines_check = (settings["plugins"] - ["harmony"] - ["publish"] - ["ValidateSceneSettings"] - ["skip_timelines_check"]) - except KeyError: - skip_resolution_check = [] - skip_timelines_check = [] - - if (any(re.search(pattern, os.getenv('AVALON_TASK')) - for pattern in skip_resolution_check)): - scene_data.pop("resolutionWidth") - scene_data.pop("resolutionHeight") - - if (any(re.search(pattern, entity_type) - for pattern in skip_timelines_check)): - scene_data.pop('frameStart', None) - scene_data.pop('frameEnd', None) return scene_data diff --git a/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py b/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py index c5cd141636..0371e80095 100644 --- a/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py +++ b/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py @@ -56,12 +56,25 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): def process(self, instance): """Plugin entry point.""" expected_settings = openpype.hosts.harmony.api.get_asset_settings() - self.log.info(expected_settings) + self.log.info("scene settings from DB:".format(expected_settings)) expected_settings = _update_frames(dict.copy(expected_settings)) expected_settings["frameEndHandle"] = expected_settings["frameEnd"] +\ expected_settings["handleEnd"] + if (any(re.search(pattern, os.getenv('AVALON_TASK')) + for pattern in self.skip_resolution_check)): + expected_settings.pop("resolutionWidth") + expected_settings.pop("resolutionHeight") + + entity_type = expected_settings.get("entityType") + if (any(re.search(pattern, entity_type) + for pattern in self.skip_timelines_check)): + expected_settings.pop('frameStart', None) + expected_settings.pop('frameEnd', None) + + expected_settings.pop("entityType") # not useful after the check + asset_name = instance.context.data['anatomyData']['asset'] if any(re.search(pattern, asset_name) for pattern in self.frame_check_filter): @@ -74,7 +87,7 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): fps = float( "{:.2f}".format(instance.context.data.get("frameRate"))) - self.log.debug(expected_settings) + self.log.debug("filtered settings: {}".format(expected_settings)) current_settings = { "fps": fps, @@ -86,7 +99,7 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): "resolutionWidth": instance.context.data.get("resolutionWidth"), "resolutionHeight": instance.context.data.get("resolutionHeight"), } - self.log.debug("curr:: {}".format(current_settings)) + self.log.debug("current scene settings {}".format(current_settings)) invalid_settings = [] for key, value in expected_settings.items(): From a11d5c4193ee2d0b270f4cce7b0c97c89619c529 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 13 May 2021 14:52:56 +0200 Subject: [PATCH 39/53] fix tests --- tests/igniter/test_bootstrap_repos.py | 280 +++++++++--------- .../lib/test_user_settings.py | 8 +- 2 files changed, 148 insertions(+), 140 deletions(-) rename tests/{pype => openpype}/lib/test_user_settings.py (95%) diff --git a/tests/igniter/test_bootstrap_repos.py b/tests/igniter/test_bootstrap_repos.py index 6c70380ab6..363efb815c 100644 --- a/tests/igniter/test_bootstrap_repos.py +++ b/tests/igniter/test_bootstrap_repos.py @@ -5,72 +5,75 @@ import sys from collections import namedtuple from pathlib import Path from zipfile import ZipFile +from uuid import uuid4 import appdirs import pytest from igniter.bootstrap_repos import BootstrapRepos -from igniter.bootstrap_repos import PypeVersion -from pype.lib import OpenPypeSettingsRegistry +from igniter.bootstrap_repos import OpenPypeVersion +from igniter.user_settings import OpenPypeSettingsRegistry @pytest.fixture def fix_bootstrap(tmp_path, pytestconfig): + """This will fix BoostrapRepos with temp paths.""" bs = BootstrapRepos() bs.live_repo_dir = pytestconfig.rootpath / 'repos' bs.data_dir = tmp_path return bs -def test_pype_version(): - v1 = PypeVersion(1, 2, 3) +def test_openpype_version(): + """Test determination of OpenPype versions.""" + v1 = OpenPypeVersion(1, 2, 3) assert str(v1) == "1.2.3" - v2 = PypeVersion(1, 2, 3, client="x") + v2 = OpenPypeVersion(1, 2, 3, client="x") assert str(v2) == "1.2.3-x" assert v1 < v2 - v3 = PypeVersion(1, 2, 3, variant="staging") + v3 = OpenPypeVersion(1, 2, 3, variant="staging") assert str(v3) == "1.2.3-staging" - v4 = PypeVersion(1, 2, 3, variant="staging", client="client") + v4 = OpenPypeVersion(1, 2, 3, variant="staging", client="client") assert str(v4) == "1.2.3-client-staging" assert v3 < v4 assert v1 < v4 - v5 = PypeVersion(1, 2, 3, variant="foo", client="x") + v5 = OpenPypeVersion(1, 2, 3, variant="foo", client="x") assert str(v5) == "1.2.3-x" assert v4 < v5 - v6 = PypeVersion(1, 2, 3, variant="foo") + v6 = OpenPypeVersion(1, 2, 3, variant="foo") assert str(v6) == "1.2.3" - v7 = PypeVersion(2, 0, 0) + v7 = OpenPypeVersion(2, 0, 0) assert v1 < v7 - v8 = PypeVersion(0, 1, 5) + v8 = OpenPypeVersion(0, 1, 5) assert v8 < v7 - v9 = PypeVersion(1, 2, 4) + v9 = OpenPypeVersion(1, 2, 4) assert v9 > v1 - v10 = PypeVersion(1, 2, 2) + v10 = OpenPypeVersion(1, 2, 2) assert v10 < v1 - v11 = PypeVersion(1, 2, 3, path=Path("/foo/bar")) + v11 = OpenPypeVersion(1, 2, 3, path=Path("/foo/bar")) assert v10 < v11 assert v5 == v2 sort_versions = [ - PypeVersion(3, 2, 1), - PypeVersion(1, 2, 3), - PypeVersion(0, 0, 1), - PypeVersion(4, 8, 10), - PypeVersion(4, 8, 20), - PypeVersion(4, 8, 9), - PypeVersion(1, 2, 3, variant="staging"), - PypeVersion(1, 2, 3, client="client") + OpenPypeVersion(3, 2, 1), + OpenPypeVersion(1, 2, 3), + OpenPypeVersion(0, 0, 1), + OpenPypeVersion(4, 8, 10), + OpenPypeVersion(4, 8, 20), + OpenPypeVersion(4, 8, 9), + OpenPypeVersion(1, 2, 3, variant="staging"), + OpenPypeVersion(1, 2, 3, client="client") ] res = sorted(sort_versions) @@ -88,25 +91,22 @@ def test_pype_version(): "5.6.3", "5.6.3-staging" ] - res_versions = [] - for v in str_versions: - res_versions.append(PypeVersion(version=v)) - + res_versions = [OpenPypeVersion(version=v) for v in str_versions] sorted_res_versions = sorted(res_versions) assert str(sorted_res_versions[0]) == str_versions[0] assert str(sorted_res_versions[-1]) == str_versions[5] with pytest.raises(ValueError): - _ = PypeVersion() + _ = OpenPypeVersion() with pytest.raises(ValueError): - _ = PypeVersion(major=1) + _ = OpenPypeVersion(major=1) with pytest.raises(ValueError): - _ = PypeVersion(version="booobaa") + _ = OpenPypeVersion(version="booobaa") - v11 = PypeVersion(version="4.6.7-client-staging") + v11 = OpenPypeVersion(version="4.6.7-client-staging") assert v11.major == 4 assert v11.minor == 6 assert v11.subversion == 7 @@ -115,15 +115,15 @@ def test_pype_version(): def test_get_main_version(): - ver = PypeVersion(1, 2, 3, variant="staging", client="foo") + ver = OpenPypeVersion(1, 2, 3, variant="staging", client="foo") assert ver.get_main_version() == "1.2.3" def test_get_version_path_from_list(): versions = [ - PypeVersion(1, 2, 3, path=Path('/foo/bar')), - PypeVersion(3, 4, 5, variant="staging", path=Path("/bar/baz")), - PypeVersion(6, 7, 8, client="x", path=Path("boo/goo")) + OpenPypeVersion(1, 2, 3, path=Path('/foo/bar')), + OpenPypeVersion(3, 4, 5, variant="staging", path=Path("/bar/baz")), + OpenPypeVersion(6, 7, 8, client="x", path=Path("boo/goo")) ] path = BootstrapRepos.get_version_path_from_list( "3.4.5-staging", versions) @@ -131,7 +131,7 @@ def test_get_version_path_from_list(): assert path == Path("/bar/baz") -def test_search_string_for_pype_version(printer): +def test_search_string_for_openpype_version(printer): strings = [ ("3.0.1", True), ("foo-3.0", False), @@ -142,106 +142,111 @@ def test_search_string_for_pype_version(printer): ] for ver_string in strings: printer(f"testing {ver_string[0]} should be {ver_string[1]}") - assert PypeVersion.version_in_str(ver_string[0])[0] == ver_string[1] + assert OpenPypeVersion.version_in_str(ver_string[0])[0] == ver_string[1] @pytest.mark.slow -def test_install_live_repos(fix_bootstrap, printer): - pype_version = fix_bootstrap.create_version_from_live_code() +def test_install_live_repos(fix_bootstrap, printer, monkeypatch, pytestconfig): + monkeypatch.setenv("OPENPYPE_ROOT", pytestconfig.rootpath.as_posix()) + monkeypatch.setenv("OPENPYPE_DATABASE_NAME", str(uuid4())) + openpype_version = fix_bootstrap.create_version_from_live_code() sep = os.path.sep expected_paths = [ - f"{pype_version.path}{sep}repos{sep}avalon-core", - f"{pype_version.path}{sep}repos{sep}avalon-unreal-integration", - f"{pype_version.path}" + f"{openpype_version.path}{sep}repos{sep}avalon-core", + f"{openpype_version.path}{sep}repos{sep}avalon-unreal-integration", + f"{openpype_version.path}" ] printer("testing zip creation") - assert os.path.exists(pype_version.path), "zip archive was not created" - fix_bootstrap.add_paths_from_archive(pype_version.path) + assert os.path.exists(openpype_version.path), "zip archive was not created" + fix_bootstrap.add_paths_from_archive(openpype_version.path) for ep in expected_paths: assert ep in sys.path, f"{ep} not set correctly" - printer("testing pype imported") - del sys.modules["pype"] - import pype # noqa: F401 + printer("testing openpype imported") + try: + del sys.modules["openpype"] + except KeyError: + # wasn't imported before + pass + import openpype # noqa: F401 - # test if pype is imported from specific location in zip - assert "pype" in sys.modules.keys(), "Pype not imported" - assert sys.modules["pype"].__file__ == \ - f"{pype_version.path}{sep}pype{sep}__init__.py" + # test if openpype is imported from specific location in zip + assert "openpype" in sys.modules.keys(), "OpenPype not imported" + assert sys.modules["openpype"].__file__ == \ + f"{openpype_version.path}{sep}openpype{sep}__init__.py" -def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): - - test_pype = namedtuple("Pype", "prefix version suffix type valid") +def test_find_openpype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): + test_openpype = namedtuple("OpenPype", "prefix version suffix type valid") test_versions_1 = [ - test_pype(prefix="foo-v", version="5.5.1", - suffix=".zip", type="zip", valid=False), - test_pype(prefix="bar-v", version="5.5.2-client", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="baz-v", version="5.5.3-client-strange", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="bum-v", version="5.5.4-staging", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="zum-v", version="5.5.5-client-staging", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="fam-v", version="5.6.3", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="foo-v", version="5.6.3-staging", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="fim-v", version="5.6.3", - suffix=".zip", type="zip", valid=False), - test_pype(prefix="foo-v", version="5.6.4", - suffix=".txt", type="txt", valid=False), - test_pype(prefix="foo-v", version="5.7.1", - suffix="", type="dir", valid=False), + test_openpype(prefix="foo-v", version="5.5.1", + suffix=".zip", type="zip", valid=False), + test_openpype(prefix="bar-v", version="5.5.2-client", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="baz-v", version="5.5.3-client-strange", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="bum-v", version="5.5.4-staging", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="zum-v", version="5.5.5-client-staging", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="fam-v", version="5.6.3", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="foo-v", version="5.6.3-staging", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="fim-v", version="5.6.3", + suffix=".zip", type="zip", valid=False), + test_openpype(prefix="foo-v", version="5.6.4", + suffix=".txt", type="txt", valid=False), + test_openpype(prefix="foo-v", version="5.7.1", + suffix="", type="dir", valid=False), ] test_versions_2 = [ - test_pype(prefix="foo-v", version="10.0.0", - suffix=".txt", type="txt", valid=False), - test_pype(prefix="lom-v", version="7.2.6", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="bom-v", version="7.2.7-client", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="woo-v", version="7.2.8-client-strange", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="loo-v", version="7.2.10-client-staging", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="kok-v", version="7.0.1", - suffix=".zip", type="zip", valid=True) + test_openpype(prefix="foo-v", version="10.0.0", + suffix=".txt", type="txt", valid=False), + test_openpype(prefix="lom-v", version="7.2.6", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="bom-v", version="7.2.7-client", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="woo-v", version="7.2.8-client-strange", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="loo-v", version="7.2.10-client-staging", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="kok-v", version="7.0.1", + suffix=".zip", type="zip", valid=True) ] test_versions_3 = [ - test_pype(prefix="foo-v", version="3.0.0", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="goo-v", version="3.0.1", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="hoo-v", version="4.1.0", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="foo-v", version="4.1.2", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="foo-v", version="3.0.1-client", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="foo-v", version="3.0.1-client-strange", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="foo-v", version="3.0.1-staging", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="foo-v", version="3.0.1-client-staging", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="foo-v", version="3.2.0", - suffix=".zip", type="zip", valid=True) + test_openpype(prefix="foo-v", version="3.0.0", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="goo-v", version="3.0.1", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="hoo-v", version="4.1.0", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="foo-v", version="4.1.2", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="foo-v", version="3.0.1-client", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="foo-v", version="3.0.1-client-strange", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="foo-v", version="3.0.1-staging", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="foo-v", version="3.0.1-client-staging", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="foo-v", version="3.2.0", + suffix=".zip", type="zip", valid=True) ] test_versions_4 = [ - test_pype(prefix="foo-v", version="10.0.0", - suffix="", type="dir", valid=True), - test_pype(prefix="lom-v", version="11.2.6", - suffix=".zip", type="dir", valid=False), - test_pype(prefix="bom-v", version="7.2.7-client", - suffix=".zip", type="zip", valid=True), - test_pype(prefix="woo-v", version="7.2.8-client-strange", - suffix=".zip", type="txt", valid=False) + test_openpype(prefix="foo-v", version="10.0.0", + suffix="", type="dir", valid=True), + test_openpype(prefix="lom-v", version="11.2.6", + suffix=".zip", type="dir", valid=False), + test_openpype(prefix="bom-v", version="7.2.7-client", + suffix=".zip", type="zip", valid=True), + test_openpype(prefix="woo-v", version="7.2.8-client-strange", + suffix=".zip", type="txt", valid=False) ] def _create_invalid_zip(path: Path): @@ -251,7 +256,7 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): def _create_valid_zip(path: Path, version: str): with ZipFile(path, "w") as zf: zf.writestr( - "pype/version.py", f"__version__ = '{version}'\n\n") + "openpype/version.py", f"__version__ = '{version}'\n\n") def _create_invalid_dir(path: Path): path.mkdir(parents=True, exist_ok=True) @@ -259,9 +264,9 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): fp.write("invalid") def _create_valid_dir(path: Path, version: str): - pype_path = path / "pype" - version_path = pype_path / "version.py" - pype_path.mkdir(parents=True, exist_ok=True) + openpype_path = path / "openpype" + version_path = openpype_path / "version.py" + openpype_path.mkdir(parents=True, exist_ok=True) with open(version_path, "w") as fp: fp.write(f"__version__ = '{version}'\n\n") @@ -283,15 +288,15 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): with open(test_path, "w") as fp: fp.write("foo") - # in PYPE_PATH + # in OPENPYPE_PATH e_path = tmp_path_factory.mktemp("environ") # create files and directories for test for test_file in test_versions_1: _build_test_item(e_path, test_file) - # in pypePath registry - p_path = tmp_path_factory.mktemp("pypePath") + # in openPypePath registry + p_path = tmp_path_factory.mktemp("openPypePath") for test_file in test_versions_2: _build_test_item(p_path, test_file) @@ -310,10 +315,10 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): for test_file in test_versions_4: _build_test_item(dir_path, test_file) - printer("testing finding Pype in given path ...") - result = fix_bootstrap.find_pype(g_path, include_zips=True) + printer("testing finding OpenPype in given path ...") + result = fix_bootstrap.find_openpype(g_path, include_zips=True) # we should have results as file were created - assert result is not None, "no Pype version found" + assert result is not None, "no OpenPype version found" # latest item in `result` should be latest version found. expected_path = Path( g_path / "{}{}{}".format( @@ -323,13 +328,13 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): ) ) assert result, "nothing found" - assert result[-1].path == expected_path, "not a latest version of Pype 3" + assert result[-1].path == expected_path, "not a latest version of OpenPype 3" - monkeypatch.setenv("PYPE_PATH", e_path.as_posix()) + monkeypatch.setenv("OPENPYPE_PATH", e_path.as_posix()) - result = fix_bootstrap.find_pype(include_zips=True) + result = fix_bootstrap.find_openpype(include_zips=True) # we should have results as file were created - assert result is not None, "no Pype version found" + assert result is not None, "no OpenPype version found" # latest item in `result` should be latest version found. expected_path = Path( e_path / "{}{}{}".format( @@ -339,21 +344,22 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): ) ) assert result, "nothing found" - assert result[-1].path == expected_path, "not a latest version of Pype 1" + assert result[-1].path == expected_path, "not a latest version of OpenPype 1" - monkeypatch.delenv("PYPE_PATH", raising=False) + monkeypatch.delenv("OPENPYPE_PATH", raising=False) # mock appdirs user_data_dir def mock_user_data_dir(*args, **kwargs): + """Mock local app data dir.""" return d_path.as_posix() monkeypatch.setattr(appdirs, "user_data_dir", mock_user_data_dir) fix_bootstrap.registry = OpenPypeSettingsRegistry() - fix_bootstrap.registry.set_item("pypePath", d_path.as_posix()) + fix_bootstrap.registry.set_item("openPypePath", d_path.as_posix()) - result = fix_bootstrap.find_pype(include_zips=True) + result = fix_bootstrap.find_openpype(include_zips=True) # we should have results as file were created - assert result is not None, "no Pype version found" + assert result is not None, "no OpenPype version found" # latest item in `result` should be latest version found. expected_path = Path( d_path / "{}{}{}".format( @@ -363,10 +369,10 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): ) ) assert result, "nothing found" - assert result[-1].path == expected_path, "not a latest version of Pype 2" + assert result[-1].path == expected_path, "not a latest version of OpenPype 2" - result = fix_bootstrap.find_pype(e_path, include_zips=True) - assert result is not None, "no Pype version found" + result = fix_bootstrap.find_openpype(e_path, include_zips=True) + assert result is not None, "no OpenPype version found" expected_path = Path( e_path / "{}{}{}".format( test_versions_1[5].prefix, @@ -374,10 +380,10 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): test_versions_1[5].suffix ) ) - assert result[-1].path == expected_path, "not a latest version of Pype 1" + assert result[-1].path == expected_path, "not a latest version of OpenPype 1" - result = fix_bootstrap.find_pype(dir_path, include_zips=True) - assert result is not None, "no Pype versions found" + result = fix_bootstrap.find_openpype(dir_path, include_zips=True) + assert result is not None, "no OpenPype versions found" expected_path = Path( dir_path / "{}{}{}".format( test_versions_4[0].prefix, @@ -385,4 +391,4 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): test_versions_4[0].suffix ) ) - assert result[-1].path == expected_path, "not a latest version of Pype 4" + assert result[-1].path == expected_path, "not a latest version of OpenPype 4" diff --git a/tests/pype/lib/test_user_settings.py b/tests/openpype/lib/test_user_settings.py similarity index 95% rename from tests/pype/lib/test_user_settings.py rename to tests/openpype/lib/test_user_settings.py index 02342abbc9..2c58e1f35a 100644 --- a/tests/pype/lib/test_user_settings.py +++ b/tests/openpype/lib/test_user_settings.py @@ -1,5 +1,7 @@ +# -*- coding: utf-8 -*- +"""Test suite for User Settings.""" import pytest -from pype.lib import ( +from igniter.user_settings import ( IniSettingRegistry, JSONSettingRegistry, OpenPypeSecureRegistry @@ -9,9 +11,9 @@ import configparser @pytest.fixture -def secure_registry(tmpdir): +def secure_registry(): name = "pypetest_{}".format(str(uuid4())) - r = OpenPypeSecureRegistry(name, tmpdir) + r = OpenPypeSecureRegistry(name) yield r From fdfb0064aa1ef6550c8313bead55bc7d95b8dfe9 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 13 May 2021 14:58:19 +0200 Subject: [PATCH 40/53] fix hound --- tests/igniter/test_bootstrap_repos.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tests/igniter/test_bootstrap_repos.py b/tests/igniter/test_bootstrap_repos.py index 363efb815c..54950c546e 100644 --- a/tests/igniter/test_bootstrap_repos.py +++ b/tests/igniter/test_bootstrap_repos.py @@ -142,7 +142,8 @@ def test_search_string_for_openpype_version(printer): ] for ver_string in strings: printer(f"testing {ver_string[0]} should be {ver_string[1]}") - assert OpenPypeVersion.version_in_str(ver_string[0])[0] == ver_string[1] + assert OpenPypeVersion.version_in_str(ver_string[0])[0] == \ + ver_string[1] @pytest.mark.slow @@ -173,7 +174,7 @@ def test_install_live_repos(fix_bootstrap, printer, monkeypatch, pytestconfig): # test if openpype is imported from specific location in zip assert "openpype" in sys.modules.keys(), "OpenPype not imported" assert sys.modules["openpype"].__file__ == \ - f"{openpype_version.path}{sep}openpype{sep}__init__.py" + f"{openpype_version.path}{sep}openpype{sep}__init__.py" def test_find_openpype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): @@ -328,7 +329,8 @@ def test_find_openpype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): ) ) assert result, "nothing found" - assert result[-1].path == expected_path, "not a latest version of OpenPype 3" + assert result[-1].path == expected_path, ("not a latest version of " + "OpenPype 3") monkeypatch.setenv("OPENPYPE_PATH", e_path.as_posix()) @@ -344,7 +346,8 @@ def test_find_openpype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): ) ) assert result, "nothing found" - assert result[-1].path == expected_path, "not a latest version of OpenPype 1" + assert result[-1].path == expected_path, ("not a latest version of " + "OpenPype 1") monkeypatch.delenv("OPENPYPE_PATH", raising=False) @@ -369,7 +372,8 @@ def test_find_openpype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): ) ) assert result, "nothing found" - assert result[-1].path == expected_path, "not a latest version of OpenPype 2" + assert result[-1].path == expected_path, ("not a latest version of " + "OpenPype 2") result = fix_bootstrap.find_openpype(e_path, include_zips=True) assert result is not None, "no OpenPype version found" @@ -380,7 +384,8 @@ def test_find_openpype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): test_versions_1[5].suffix ) ) - assert result[-1].path == expected_path, "not a latest version of OpenPype 1" + assert result[-1].path == expected_path, ("not a latest version of " + "OpenPype 1") result = fix_bootstrap.find_openpype(dir_path, include_zips=True) assert result is not None, "no OpenPype versions found" @@ -391,4 +396,5 @@ def test_find_openpype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): test_versions_4[0].suffix ) ) - assert result[-1].path == expected_path, "not a latest version of OpenPype 4" + assert result[-1].path == expected_path, ("not a latest version of " + "OpenPype 4") From c6bd91d7a474d7c1e377f6d271c4a1d70a2fbbe9 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 13 May 2021 15:30:12 +0200 Subject: [PATCH 41/53] fix test script on linux --- tools/run_tests.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/run_tests.sh b/tools/run_tests.sh index 5488be9430..3620ebc0e5 100755 --- a/tools/run_tests.sh +++ b/tools/run_tests.sh @@ -122,5 +122,4 @@ main () { PYTHONPATH=$original_pythonpath } - - +main From 876efd0bc4b41010d5c5395dc58fa9d2bb852429 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 13 May 2021 16:38:53 +0200 Subject: [PATCH 42/53] AE - added documentation for Settings --- website/docs/admin_hosts_aftereffects.md | 39 ++++++++++++++++++ .../admin_hosts_aftereffects_settings.png | Bin 0 -> 31766 bytes website/sidebars.js | 3 +- 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 website/docs/admin_hosts_aftereffects.md create mode 100644 website/docs/assets/admin_hosts_aftereffects_settings.png diff --git a/website/docs/admin_hosts_aftereffects.md b/website/docs/admin_hosts_aftereffects.md new file mode 100644 index 0000000000..dc43820465 --- /dev/null +++ b/website/docs/admin_hosts_aftereffects.md @@ -0,0 +1,39 @@ +--- +id: admin_hosts_aftereffects +title: AfterEffects Settings +sidebar_label: AfterEffects +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +## AfterEffects settings + +There is a couple of settings that could configure publishing process for **AfterEffects**. +All of them are Project based, eg. each project could have different configuration. + +Location: Settings > Project > AfterEffects + +![Harmony Project Settings](assets/admin_hosts_aftereffects_settings.png) + +## Publish plugins + +### Validate Scene Settings + +#### Skip Resolution Check for Tasks + +Set regex pattern(s) to look for in a Task name to skip resolution check against values from DB. + +#### Skip Timeline Check for Tasks + +Set regex pattern(s) to look for in a Task name to skip `frameStart`, `frameEnd` check against values from DB. + +### AfterEffects Submit to Deadline + +* `Use Published scene` - Set to True (green) when Deadline should take published scene as a source instead of uploaded local one. +* `Priority` - priority of job on farm +* `Primary Pool` - here is list of pool fetched from server you can select from. +* `Secondary Pool` +* `Frames Per Task` - number of sequence division between individual tasks (chunks) +making one job on farm. + diff --git a/website/docs/assets/admin_hosts_aftereffects_settings.png b/website/docs/assets/admin_hosts_aftereffects_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..9b879585f8c93026ed7389935107834d2aa3cae8 GIT binary patch literal 31766 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 z�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 Date: Thu, 13 May 2021 16:40:46 +0200 Subject: [PATCH 43/53] AE - added new schemas for settings Moved filtering from __init__ to Validator where it has more sense Added defaults --- openpype/hosts/aftereffects/api/__init__.py | 34 ------- .../publish/validate_scene_settings.py | 22 ++++- .../project_settings/aftereffects.json | 18 ++++ .../schemas/projects_schema/schema_main.json | 4 + .../schema_project_aftereffects.json | 90 +++++++++++++++++++ 5 files changed, 131 insertions(+), 37 deletions(-) create mode 100644 openpype/settings/defaults/project_settings/aftereffects.json create mode 100644 openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json diff --git a/openpype/hosts/aftereffects/api/__init__.py b/openpype/hosts/aftereffects/api/__init__.py index e914c26435..5f6a64a6d0 100644 --- a/openpype/hosts/aftereffects/api/__init__.py +++ b/openpype/hosts/aftereffects/api/__init__.py @@ -112,38 +112,4 @@ def get_asset_settings(): "duration": duration } - try: - # temporary, in pype3 replace with api.get_current_project_settings - skip_resolution_check = ( - api.get_current_project_settings() - ["plugins"] - ["aftereffects"] - ["publish"] - ["ValidateSceneSettings"] - ["skip_resolution_check"] - ) - skip_timelines_check = ( - api.get_current_project_settings() - ["plugins"] - ["aftereffects"] - ["publish"] - ["ValidateSceneSettings"] - ["skip_timelines_check"] - ) - except KeyError: - skip_resolution_check = ['*'] - skip_timelines_check = ['*'] - - if os.getenv('AVALON_TASK') in skip_resolution_check or \ - '*' in skip_timelines_check: - scene_data.pop("resolutionWidth") - scene_data.pop("resolutionHeight") - - if entity_type in skip_timelines_check or '*' in skip_timelines_check: - scene_data.pop('fps', None) - scene_data.pop('frameStart', None) - scene_data.pop('frameEnd', None) - scene_data.pop('handleStart', None) - scene_data.pop('handleEnd', None) - return scene_data diff --git a/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py b/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py index cc7db3141f..5301a2f3ea 100644 --- a/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py +++ b/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """Validate scene settings.""" import os +import re import pyblish.api @@ -56,13 +57,26 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): hosts = ["aftereffects"] optional = True - skip_timelines_check = ["*"] # * >> skip for all - skip_resolution_check = ["*"] + skip_timelines_check = [".*"] # * >> skip for all + skip_resolution_check = [".*"] def process(self, instance): """Plugin entry point.""" expected_settings = api.get_asset_settings() - self.log.info("expected_settings::{}".format(expected_settings)) + self.log.info("config from DB::{}".format(expected_settings)) + + if any(re.search(pattern, os.getenv('AVALON_TASK')) + for pattern in self.skip_resolution_check): + expected_settings.pop("resolutionWidth") + expected_settings.pop("resolutionHeight") + + if any(re.search(pattern, os.getenv('AVALON_TASK')) + for pattern in self.skip_timelines_check): + expected_settings.pop('fps', None) + expected_settings.pop('frameStart', None) + expected_settings.pop('frameEnd', None) + expected_settings.pop('handleStart', None) + expected_settings.pop('handleEnd', None) # handle case where ftrack uses only two decimal places # 23.976023976023978 vs. 23.98 @@ -76,6 +90,8 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): duration = instance.data.get("frameEndHandle") - \ instance.data.get("frameStartHandle") + 1 + self.log.debug("filtered config::{}".format(expected_settings)) + current_settings = { "fps": fps, "frameStartHandle": instance.data.get("frameStartHandle"), diff --git a/openpype/settings/defaults/project_settings/aftereffects.json b/openpype/settings/defaults/project_settings/aftereffects.json new file mode 100644 index 0000000000..f54dbb9612 --- /dev/null +++ b/openpype/settings/defaults/project_settings/aftereffects.json @@ -0,0 +1,18 @@ +{ + "publish": { + "ValidateSceneSettings": { + "enabled": true, + "optional": true, + "active": true, + "skip_resolution_check": [".*"], + "skip_timelines_check": [".*"] + }, + "AfterEffectsSubmitDeadline": { + "use_published": true, + "priority": 50, + "primary_pool": "", + "secondary_pool": "", + "chunk_size": 1000000 + } + } +} \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json index 6bc158aa60..b4666b302a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_main.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json @@ -78,6 +78,10 @@ "type": "schema", "name": "schema_project_hiero" }, + { + "type": "schema", + "name": "schema_project_aftereffects" + }, { "type": "schema", "name": "schema_project_harmony" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json new file mode 100644 index 0000000000..63bf9274a3 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json @@ -0,0 +1,90 @@ +{ + "type": "dict", + "collapsible": true, + "key": "aftereffects", + "label": "AfterEffects", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsible": true, + "key": "publish", + "label": "Publish plugins", + "children": [ + { + "type": "dict", + "collapsible": true, + "key": "ValidateSceneSettings", + "label": "Validate Scene Settings", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + }, + { + "type": "label", + "label": "Validate if FPS and Resolution match shot data" + }, + { + "type": "list", + "key": "skip_resolution_check", + "object_type": "text", + "label": "Skip Resolution Check for Tasks" + }, + { + "type": "list", + "key": "skip_timelines_check", + "object_type": "text", + "label": "Skip Timeline Check for Tasks" + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "AfterEffectsSubmitDeadline", + "label": "AfterEffects Submit to Deadline", + "children": [ + { + "type": "boolean", + "key": "use_published", + "label": "Use Published scene" + }, + { + "type": "number", + "key": "priority", + "label": "Priority" + }, + { + "type": "text", + "key": "primary_pool", + "label": "Primary Pool" + }, + { + "type": "text", + "key": "secondary_pool", + "label": "Secondary Pool" + }, + { + "type": "number", + "key": "chunk_size", + "label": "Frames Per Task" + } + ] + } + ] + } + ] +} From c82ab41d7c421f7dcef7bb4116b6cc37f5d5f605 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 13 May 2021 16:46:27 +0200 Subject: [PATCH 44/53] Harmony - modified setting schema to allow enabling, setting optionality --- .../schema_project_harmony.json | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json index 8b4e379691..8b5d638cd8 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json @@ -30,7 +30,27 @@ "collapsible": true, "key": "ValidateSceneSettings", "label": "Validate Scene Settings", + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + }, + { + "type": "label", + "label": "Validate if FrameStart, FrameEnd and Resolution match shot data" + }, { "type": "list", "key": "frame_check_filter", From 988d843b258e8a4c515cd4e4f83b6ed3b72ef79a Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 14 May 2021 13:27:55 +0200 Subject: [PATCH 45/53] cast representations log into string --- openpype/plugins/publish/extract_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index f341ba197f..048d16fabb 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -55,7 +55,7 @@ class ExtractReview(pyblish.api.InstancePlugin): profiles = None def process(self, instance): - self.log.debug(instance.data["representations"]) + self.log.debug(str(instance.data["representations"])) # Skip review when requested. if not instance.data.get("review", True): return From 7171a87a4bf73e2738c0488b0bbd34826f47feaf Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 14 May 2021 13:28:38 +0200 Subject: [PATCH 46/53] get explicit collections of playblasted files --- .../maya/plugins/publish/extract_playblast.py | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 358fca6c2a..f79c4a1698 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -96,19 +96,26 @@ class ExtractPlayblast(openpype.api.Extractor): # Remove panel key since it's internal value to capture_gui preset.pop("panel", None) - self.log.info('using viewport preset: {}'.format(preset)) path = capture.capture(**preset) playblast = self._fix_playblast_output_path(path) - self.log.info("file list {}".format(playblast)) + self.log.debug("playblast path {}".format(path)) - collected_frames = os.listdir(stagingdir) - collections, remainder = clique.assemble(collected_frames) - input_path = os.path.join( - stagingdir, collections[0].format('{head}{padding}{tail}')) - self.log.info("input {}".format(input_path)) + collected_files = os.listdir(stagingdir) + collections, remainder = clique.assemble(collected_files) + + self.log.debug("filename {}".format(filename)) + frame_collection = None + for collection in collections: + filebase = collection.format('{head}').rstrip(".") + self.log.debug("collection head {}".format(filebase)) + if filebase in filename: + frame_collection = collection + self.log.info( + "we found collection of interest {}".format( + str(frame_collection))) if "representations" not in instance.data: instance.data["representations"] = [] @@ -119,12 +126,11 @@ class ExtractPlayblast(openpype.api.Extractor): # Add camera node name to representation data camera_node_name = pm.ls(camera)[0].getTransform().name() - representation = { 'name': 'png', 'ext': 'png', - 'files': collected_frames, + 'files': list(frame_collection), "stagingDir": stagingdir, "frameStart": start, "frameEnd": end, From 153dc4b1aad64d8ff54a287c844a7d2e32b598bd Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 14 May 2021 13:28:57 +0200 Subject: [PATCH 47/53] remove obsolete function _fix_playblast_output_path --- .../maya/plugins/publish/extract_playblast.py | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index f79c4a1698..0dc91d67a9 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -99,7 +99,6 @@ class ExtractPlayblast(openpype.api.Extractor): self.log.info('using viewport preset: {}'.format(preset)) path = capture.capture(**preset) - playblast = self._fix_playblast_output_path(path) self.log.debug("playblast path {}".format(path)) @@ -141,44 +140,6 @@ class ExtractPlayblast(openpype.api.Extractor): } instance.data["representations"].append(representation) - def _fix_playblast_output_path(self, filepath): - """Workaround a bug in maya.cmds.playblast to return correct filepath. - - When the `viewer` argument is set to False and maya.cmds.playblast - does not automatically open the playblasted file the returned - filepath does not have the file's extension added correctly. - - To workaround this we just glob.glob() for any file extensions and - assume the latest modified file is the correct file and return it. - """ - # Catch cancelled playblast - if filepath is None: - self.log.warning("Playblast did not result in output path. " - "Playblast is probably interrupted.") - return None - - # Fix: playblast not returning correct filename (with extension) - # Lets assume the most recently modified file is the correct one. - if not os.path.exists(filepath): - directory = os.path.dirname(filepath) - filename = os.path.basename(filepath) - # check if the filepath is has frame based filename - # example : capture.####.png - parts = filename.split(".") - if len(parts) == 3: - query = os.path.join(directory, "{}.*.{}".format(parts[0], - parts[-1])) - files = glob.glob(query) - else: - files = glob.glob("{}.*".format(filepath)) - - if not files: - raise RuntimeError("Couldn't find playblast from: " - "{0}".format(filepath)) - filepath = max(files, key=os.path.getmtime) - - return filepath - @contextlib.contextmanager def maintained_time(): From ecd498df857eddcb3fc98a731ce6175152d00818 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 14:11:59 +0200 Subject: [PATCH 48/53] change pyblish MessageHandler emit method to convert messages to string at the moment of emmiting --- openpype/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openpype/__init__.py b/openpype/__init__.py index f63d534e08..a86d2bc2be 100644 --- a/openpype/__init__.py +++ b/openpype/__init__.py @@ -67,6 +67,15 @@ def patched_discover(superclass): @import_wrapper def install(): """Install Pype to Avalon.""" + from pyblish.lib import MessageHandler + + def modified_emit(obj, record): + """Method replacing `emit` in Pyblish's MessageHandler.""" + record.msg = record.getMessage() + obj.records.append(record) + + MessageHandler.emit = modified_emit + log.info("Registering global plug-ins..") pyblish.register_plugin_path(PUBLISH_PATH) pyblish.register_discovery_filter(filter_pyblish_plugins) From 2d6bb1108aeed2a0e1cc00587e2c37c52d8fa054 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 14 May 2021 16:33:28 +0200 Subject: [PATCH 49/53] SyncServer - fixes for revalidating cache for sync settings Fix wrong methods --- .../modules/sync_server/providers/gdrive.py | 1 - .../modules/sync_server/sync_server_module.py | 120 +++++++++++------- 2 files changed, 74 insertions(+), 47 deletions(-) diff --git a/openpype/modules/sync_server/providers/gdrive.py b/openpype/modules/sync_server/providers/gdrive.py index 65fccb3215..2caf41ba5e 100644 --- a/openpype/modules/sync_server/providers/gdrive.py +++ b/openpype/modules/sync_server/providers/gdrive.py @@ -4,7 +4,6 @@ import time import sys from setuptools.extern import six import platform -from json.decoder import JSONDecodeError from openpype.api import Logger from openpype.api import get_system_settings diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index aefe8195c4..a9f1a73d77 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -3,6 +3,7 @@ from bson.objectid import ObjectId from datetime import datetime import threading import platform +import copy from avalon.api import AvalonMongoDB @@ -15,7 +16,8 @@ from openpype.api import ( from openpype.lib import PypeLogger from openpype.settings.lib import ( get_default_project_settings, - get_default_anatomy_settings) + get_default_anatomy_settings, + get_anatomy_settings) from .providers.local_drive import LocalDriveHandler from .providers import lib @@ -396,9 +398,13 @@ class SyncServerModule(PypeModule, ITrayModule): return remote_site + def get_local_settings_schema(self): + """Wrapper for Local settings - all projects incl. Default""" + return self.get_configurable_items(EditableScopes.LOCAL) + def get_configurable_items(self, scope=None): """ - Returns list of items that could be configurable for all projects. + Returns list of sites that could be configurable for all projects. Could be filtered by 'scope' argument (list) @@ -443,8 +449,9 @@ class SyncServerModule(PypeModule, ITrayModule): return editable def get_local_settings_schema_for_project(self, project_name): - """Wrapper for Local settings""" - return self.get_configurable_items(project_name, EditableScopes.LOCAL) + """Wrapper for Local settings - for specific 'project_name'""" + return self.get_configurable_items_for_project(project_name, + EditableScopes.LOCAL) def get_configurable_items_for_project(self, project_name=None, scope=None): @@ -480,7 +487,7 @@ class SyncServerModule(PypeModule, ITrayModule): } """ allowed_sites = set() - sites = self.get_all_sites(project_name) + sites = self.get_all_site_configs(project_name) if project_name: # Local Settings can select only from allowed sites for project allowed_sites.update(set(self.get_active_sites(project_name))) @@ -502,10 +509,10 @@ class SyncServerModule(PypeModule, ITrayModule): return editable def get_local_settings_schema_for_site(self, project_name, site_name): - """Wrapper for Local settings""" - return self.get_configurable_items(project_name, - site_name, - EditableScopes.LOCAL) + """Wrapper for Local settings - for particular 'site_name and proj.""" + return self.get_configurable_items_for_site(project_name, + site_name, + EditableScopes.LOCAL) def get_configurable_items_for_site(self, project_name=None, site_name=None, @@ -537,7 +544,8 @@ class SyncServerModule(PypeModule, ITrayModule): if project_name: sync_s = self.get_sync_project_setting(project_name, - exclude_locals=True) + exclude_locals=True, + cached=False) else: sync_s = get_default_project_settings(exclude_locals=True) sync_s = sync_s["global"]["sync_server"] @@ -765,37 +773,49 @@ class SyncServerModule(PypeModule, ITrayModule): exclude_locals (bool): ignore overrides from Local Settings For performance """ - sync_project_settings = {} - - system_sites = self.get_all_sites() - - for collection in self.connection.database.collection_names(False): - sites = dict(system_sites) # get all configured sites - proj_settings = self._parse_sync_settings_from_settings( - get_project_settings(collection, - exclude_locals=exclude_locals)) - sites.update(proj_settings["sites"]) # apply project overrides - proj_settings["sites"] = sites - - sync_project_settings[collection] = proj_settings - - if not sync_project_settings: - log.info("No enabled and configured projects for sync.") + sync_project_settings = self._prepare_sync_project_settings( + exclude_locals) self._sync_project_settings = sync_project_settings - def get_sync_project_setting(self, project_name, exclude_locals=False): + def _prepare_sync_project_settings(self, exclude_locals): + sync_project_settings = {} + system_sites = self.get_all_site_configs() + for collection in self.connection.database.collection_names(False): + sites = copy.deepcopy(system_sites) # get all configured sites + proj_settings = self._parse_sync_settings_from_settings( + get_project_settings(collection, + exclude_locals=exclude_locals)) + sites.update(self._get_default_site_configs( + proj_settings["enabled"], collection)) + sites.update(proj_settings['sites']) + proj_settings["sites"] = sites + + sync_project_settings[collection] = proj_settings + if not sync_project_settings: + log.info("No enabled and configured projects for sync.") + return sync_project_settings + + def get_sync_project_setting(self, project_name, exclude_locals=False, + cached=True): """ Handles pulling sync_server's settings for enabled 'project_name' Args: project_name (str): used in project settings exclude_locals (bool): ignore overrides from Local Settings + cached (bool): use pre-cached values, or return fresh ones + cached values needed for single loop (with all overrides) + fresh values needed for Local settings (without overrides) Returns: (dict): settings dictionary for the enabled project, empty if no settings or sync is disabled """ # presets set already, do not call again and again # self.log.debug("project preset {}".format(self.presets)) + if not cached: + return self._prepare_sync_project_settings(exclude_locals)\ + [project_name] + if not self.sync_project_settings or \ not self.sync_project_settings.get(project_name): self.set_sync_project_settings(exclude_locals) @@ -808,24 +828,7 @@ class SyncServerModule(PypeModule, ITrayModule): return sync_settings - def _get_default_site_configs(self, sync_enabled=True): - """ - Returns skeleton settings for 'studio' and user's local site - """ - anatomy_sett = get_default_anatomy_settings() - roots = {} - for root, config in anatomy_sett["roots"].items(): - roots[root] = config[platform.system().lower()] - studio_config = { - 'provider': 'local_drive', - "root": roots - } - all_sites = {self.DEFAULT_SITE: studio_config} - if sync_enabled: - all_sites[get_local_site_id()] = {'provider': 'local_drive'} - return all_sites - - def get_all_sites(self, project_name=None): + def get_all_site_configs(self, project_name=None): """ Returns (dict) with all sites configured system wide. @@ -849,10 +852,35 @@ class SyncServerModule(PypeModule, ITrayModule): for site, detail in sync_sett.get("sites", {}).items(): system_sites[site] = detail - system_sites.update(self._get_default_site_configs(sync_enabled)) + system_sites.update(self._get_default_site_configs(sync_enabled, + project_name)) return system_sites + def _get_default_site_configs(self, sync_enabled=True, project_name=None): + """ + Returns settings for 'studio' and user's local site + + Returns base values from setting, not overriden by Local Settings, + eg. value used to push TO LS not to get actual value for syncing. + """ + if not project_name: + anatomy_sett = get_default_anatomy_settings(exclude_locals=True) + else: + anatomy_sett = get_anatomy_settings(project_name, + exclude_locals=True) + roots = {} + for root, config in anatomy_sett["roots"].items(): + roots[root] = config[platform.system().lower()] + studio_config = { + 'provider': 'local_drive', + "root": roots + } + all_sites = {self.DEFAULT_SITE: studio_config} + if sync_enabled: + all_sites[get_local_site_id()] = {'provider': 'local_drive'} + return all_sites + def get_provider_for_site(self, project_name=None, site=None): """ Return provider name for site (unique name across all projects. From e2b56e5217c8902c40c3525c0d07f595def089bc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 14 May 2021 19:19:23 +0200 Subject: [PATCH 50/53] SyncServer - wrong six library imported --- openpype/modules/sync_server/providers/gdrive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/providers/gdrive.py b/openpype/modules/sync_server/providers/gdrive.py index 2caf41ba5e..c05b6b9090 100644 --- a/openpype/modules/sync_server/providers/gdrive.py +++ b/openpype/modules/sync_server/providers/gdrive.py @@ -2,7 +2,7 @@ from __future__ import print_function import os.path import time import sys -from setuptools.extern import six +import six import platform from openpype.api import Logger From c05995c238803d1de5a937f5491c35dbb2247fae Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 17 May 2021 09:56:15 +0200 Subject: [PATCH 51/53] minor fixes in wording --- openpype/settings/defaults/project_settings/harmony.json | 8 +++++--- website/docs/admin_hosts_harmony.md | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/openpype/settings/defaults/project_settings/harmony.json b/openpype/settings/defaults/project_settings/harmony.json index 2131516805..0c7a35c058 100644 --- a/openpype/settings/defaults/project_settings/harmony.json +++ b/openpype/settings/defaults/project_settings/harmony.json @@ -1,14 +1,16 @@ { "publish": { "CollectPalettes": { - "allowed_tasks": [".*"] + "allowed_tasks": [ + ".*" + ] }, "ValidateSceneSettings": { "enabled": true, "optional": true, "active": true, - "frame_check_filter": ["_ch_", "_pr_", "_intd_", "_extd_"], - "skip_resolution_check": ["render", "Render"], + "frame_check_filter": [], + "skip_resolution_check": [], "skip_timelines_check": [] }, "HarmonySubmitDeadline": { diff --git a/website/docs/admin_hosts_harmony.md b/website/docs/admin_hosts_harmony.md index 756ca1c27f..2c49d8ba73 100644 --- a/website/docs/admin_hosts_harmony.md +++ b/website/docs/admin_hosts_harmony.md @@ -24,21 +24,21 @@ Location: Settings > Project > Harmony Set regex pattern(s) only for task names when publishing of Palettes should occur. -Use ".*" for publish Palettes for ALL tasks. +Use ".*" to publish Palettes for ALL tasks. ### Validate Scene Settings #### Skip Frame check for Assets with -Set regex pattern(s) to find in Asset name to skip checks of `frameEnd` value from DB. +Set regex pattern(s) for filtering Asset name that should skip validation of `frameEnd` value from DB. #### Skip Resolution Check for Tasks -Set regex pattern(s) to find in Task name to skip resolution check against values from DB. +Set regex pattern(s) for filtering Asset name that should skip validation or `Resolution` value from DB. #### Skip Timeline Check for Tasks -Set regex pattern(s) to find in Task name to skip `frameStart`, `frameEnd` check against values from DB. +Set regex pattern(s) for filtering Task name that should skip validation `frameStart`, `frameEnd` check against values from DB. ### Harmony Submit to Deadline From b5bf29b0f2aca23383e74c0bb50fd46c23d0cc8e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 17 May 2021 10:12:51 +0200 Subject: [PATCH 52/53] SyncServer - Local Settings expect 'local' instead of real site id --- openpype/modules/sync_server/sync_server_module.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index a9f1a73d77..962b1e1e53 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -492,9 +492,6 @@ class SyncServerModule(PypeModule, ITrayModule): # Local Settings can select only from allowed sites for project allowed_sites.update(set(self.get_active_sites(project_name))) allowed_sites.update(set(self.get_remote_sites(project_name))) - # Settings allow use of 'local' site, user's site is not 'local' - if 'local' in allowed_sites: - allowed_sites.add(get_local_site_id()) editable = {} for site_name in sites.keys(): @@ -878,7 +875,7 @@ class SyncServerModule(PypeModule, ITrayModule): } all_sites = {self.DEFAULT_SITE: studio_config} if sync_enabled: - all_sites[get_local_site_id()] = {'provider': 'local_drive'} + all_sites['local'] = {'provider': 'local_drive'} return all_sites def get_provider_for_site(self, project_name=None, site=None): From 2053bb7d6da87568a909cfb8ead88e90bb7258ff Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 17 May 2021 10:36:37 +0200 Subject: [PATCH 53/53] SyncServer - fill {site} placeholder Modified namespace for gdrive --- openpype/modules/sync_server/providers/gdrive.py | 2 +- openpype/modules/sync_server/sync_server_module.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/providers/gdrive.py b/openpype/modules/sync_server/providers/gdrive.py index c05b6b9090..18d679b833 100644 --- a/openpype/modules/sync_server/providers/gdrive.py +++ b/openpype/modules/sync_server/providers/gdrive.py @@ -113,7 +113,7 @@ class GDriveHandler(AbstractProvider): EditableScopes.LOCAL], 'label': "Credentials url", 'type': 'text', - 'namespace': '{project_setting}/global/sync_server/sites/{site}/{platform}' # noqa: E501 + 'namespace': '{project_settings}/global/sync_server/sites/{site}/credentials_url/{platform}' # noqa: E501 }, # roots could be override only on Project leve, User cannot 'root': {'scope': [EditableScopes.PROJECT], diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 962b1e1e53..ed403b836d 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -573,6 +573,8 @@ class SyncServerModule(PypeModule, ITrayModule): st = "{}'s field value {} should be".format(key, val) # noqa: E501 log.error(st + " multiplatform dict") + item["namespace"] = item["namespace"].replace('{site}', + site_name) children = [] if properties["type"] == "dict": if val: