mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-02 00:44:52 +01:00
Merge branch 'develop' into enhancement/maya_review
This commit is contained in:
commit
5b142967ea
1015 changed files with 37218 additions and 14639 deletions
19
.github/workflows/automate-projects.yml
vendored
19
.github/workflows/automate-projects.yml
vendored
|
|
@ -1,19 +0,0 @@
|
|||
name: Automate Projects
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, labeled]
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
jobs:
|
||||
assign_one_project:
|
||||
runs-on: ubuntu-latest
|
||||
name: Assign to One Project
|
||||
steps:
|
||||
- name: Assign NEW bugs to triage
|
||||
uses: srggrs/assign-one-project-github-action@1.2.0
|
||||
if: contains(github.event.issue.labels.*.name, 'bug')
|
||||
with:
|
||||
project: 'https://github.com/pypeclub/pype/projects/2'
|
||||
column_name: 'Needs triage'
|
||||
6
.github/workflows/milestone_assign.yml
vendored
6
.github/workflows/milestone_assign.yml
vendored
|
|
@ -13,7 +13,7 @@ jobs:
|
|||
if: github.event.pull_request.milestone == null
|
||||
uses: zoispag/action-assign-milestone@v1
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
repo-token: "${{ secrets.YNPUT_BOT_TOKEN }}"
|
||||
milestone: 'next-minor'
|
||||
|
||||
run_if_develop:
|
||||
|
|
@ -24,5 +24,5 @@ jobs:
|
|||
if: github.event.pull_request.milestone == null
|
||||
uses: zoispag/action-assign-milestone@v1
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
milestone: 'next-patch'
|
||||
repo-token: "${{ secrets.YNPUT_BOT_TOKEN }}"
|
||||
milestone: 'next-patch'
|
||||
|
|
|
|||
8
.github/workflows/milestone_create.yml
vendored
8
.github/workflows/milestone_create.yml
vendored
|
|
@ -12,7 +12,7 @@ jobs:
|
|||
uses: "WyriHaximus/github-action-get-milestones@master"
|
||||
id: milestones
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}"
|
||||
|
||||
- run: printf "name=number::%s" $(printenv MILESTONES | jq --arg MILESTONE $(printenv MILESTONE) '.[] | select(.title == $MILESTONE) | .number')
|
||||
id: querymilestone
|
||||
|
|
@ -31,7 +31,7 @@ jobs:
|
|||
with:
|
||||
title: 'next-patch'
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}"
|
||||
|
||||
generate-next-minor:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -40,7 +40,7 @@ jobs:
|
|||
uses: "WyriHaximus/github-action-get-milestones@master"
|
||||
id: milestones
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}"
|
||||
|
||||
- run: printf "name=number::%s" $(printenv MILESTONES | jq --arg MILESTONE $(printenv MILESTONE) '.[] | select(.title == $MILESTONE) | .number')
|
||||
id: querymilestone
|
||||
|
|
@ -59,4 +59,4 @@ jobs:
|
|||
with:
|
||||
title: 'next-minor'
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}"
|
||||
|
|
|
|||
47
.github/workflows/miletone_release_trigger.yml
vendored
Normal file
47
.github/workflows/miletone_release_trigger.yml
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
name: Milestone Release [trigger]
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
milestone:
|
||||
required: true
|
||||
release-type:
|
||||
type: choice
|
||||
description: What release should be created
|
||||
options:
|
||||
- release
|
||||
- pre-release
|
||||
milestone:
|
||||
types: closed
|
||||
|
||||
|
||||
jobs:
|
||||
milestone-title:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
milestone: ${{ steps.milestoneTitle.outputs.value }}
|
||||
steps:
|
||||
- name: Switch input milestone
|
||||
uses: haya14busa/action-cond@v1
|
||||
id: milestoneTitle
|
||||
with:
|
||||
cond: ${{ inputs.milestone == '' }}
|
||||
if_true: ${{ github.event.milestone.title }}
|
||||
if_false: ${{ inputs.milestone }}
|
||||
- name: Print resulted milestone
|
||||
run: |
|
||||
echo "${{ steps.milestoneTitle.outputs.value }}"
|
||||
|
||||
call-ci-tools-milestone-release:
|
||||
needs: milestone-title
|
||||
uses: ynput/ci-tools/.github/workflows/milestone_release_ref.yml@main
|
||||
with:
|
||||
milestone: ${{ needs.milestone-title.outputs.milestone }}
|
||||
repo-owner: ${{ github.event.repository.owner.login }}
|
||||
repo-name: ${{ github.event.repository.name }}
|
||||
version-py-path: "./openpype/version.py"
|
||||
pyproject-path: "./pyproject.toml"
|
||||
secrets:
|
||||
token: ${{ secrets.YNPUT_BOT_TOKEN }}
|
||||
user_email: ${{ secrets.CI_EMAIL }}
|
||||
user_name: ${{ secrets.CI_USER }}
|
||||
6
.github/workflows/nightly_merge.yml
vendored
6
.github/workflows/nightly_merge.yml
vendored
|
|
@ -14,10 +14,10 @@ jobs:
|
|||
- name: 🚛 Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: 🔨 Merge develop to main
|
||||
- name: 🔨 Merge develop to main
|
||||
uses: everlytic/branch-merge@1.1.0
|
||||
with:
|
||||
github_token: ${{ secrets.ADMIN_TOKEN }}
|
||||
github_token: ${{ secrets.YNPUT_BOT_TOKEN }}
|
||||
source_ref: 'develop'
|
||||
target_branch: 'main'
|
||||
commit_message_template: '[Automated] Merged {source_ref} into {target_branch}'
|
||||
|
|
@ -26,4 +26,4 @@ jobs:
|
|||
uses: benc-uk/workflow-dispatch@v1
|
||||
with:
|
||||
workflow: Nightly Prerelease
|
||||
token: ${{ secrets.ADMIN_TOKEN }}
|
||||
token: ${{ secrets.YNPUT_BOT_TOKEN }}
|
||||
|
|
|
|||
42
.github/workflows/prerelease.yml
vendored
42
.github/workflows/prerelease.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.7
|
||||
python-version: 3.9
|
||||
|
||||
- name: Install Python requirements
|
||||
run: pip install gitpython semver PyGithub
|
||||
|
|
@ -25,43 +25,15 @@ jobs:
|
|||
- name: 🔎 Determine next version type
|
||||
id: version_type
|
||||
run: |
|
||||
TYPE=$(python ./tools/ci_tools.py --bump --github_token ${{ secrets.GITHUB_TOKEN }})
|
||||
|
||||
echo ::set-output name=type::$TYPE
|
||||
TYPE=$(python ./tools/ci_tools.py --bump --github_token ${{ secrets.YNPUT_BOT_TOKEN }})
|
||||
echo "type=${TYPE}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 💉 Inject new version into files
|
||||
id: version
|
||||
if: steps.version_type.outputs.type != 'skip'
|
||||
run: |
|
||||
RESULT=$(python ./tools/ci_tools.py --nightly --github_token ${{ secrets.GITHUB_TOKEN }})
|
||||
|
||||
echo ::set-output name=next_tag::$RESULT
|
||||
|
||||
# - name: "✏️ Generate full changelog"
|
||||
# if: steps.version_type.outputs.type != 'skip'
|
||||
# id: generate-full-changelog
|
||||
# uses: heinrichreimer/github-changelog-generator-action@v2.3
|
||||
# with:
|
||||
# token: ${{ secrets.ADMIN_TOKEN }}
|
||||
# addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**🔀 Refactored code**", "labels":["refactor"]}}'
|
||||
# issues: false
|
||||
# issuesWoLabels: false
|
||||
# sinceTag: "3.12.0"
|
||||
# maxIssues: 100
|
||||
# pullRequests: true
|
||||
# prWoLabels: false
|
||||
# author: false
|
||||
# unreleased: true
|
||||
# compareLink: true
|
||||
# stripGeneratorNotice: true
|
||||
# verbose: true
|
||||
# unreleasedLabel: ${{ steps.version.outputs.next_tag }}
|
||||
# excludeTagsRegex: "CI/.+"
|
||||
# releaseBranch: "main"
|
||||
|
||||
- name: "🖨️ Print changelog to console"
|
||||
if: steps.version_type.outputs.type != 'skip'
|
||||
run: cat CHANGELOG.md
|
||||
NEW_VERSION_TAG=$(python ./tools/ci_tools.py --nightly --github_token ${{ secrets.YNPUT_BOT_TOKEN }})
|
||||
echo "next_tag=${NEW_VERSION_TAG}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 💾 Commit and Tag
|
||||
id: git_commit
|
||||
|
|
@ -80,7 +52,7 @@ jobs:
|
|||
- name: Push to protected main branch
|
||||
uses: CasperWA/push-protected@v2.10.0
|
||||
with:
|
||||
token: ${{ secrets.ADMIN_TOKEN }}
|
||||
token: ${{ secrets.YNPUT_BOT_TOKEN }}
|
||||
branch: main
|
||||
tags: true
|
||||
unprotect_reviews: true
|
||||
|
|
@ -89,7 +61,7 @@ jobs:
|
|||
uses: everlytic/branch-merge@1.1.0
|
||||
if: steps.version_type.outputs.type != 'skip'
|
||||
with:
|
||||
github_token: ${{ secrets.ADMIN_TOKEN }}
|
||||
github_token: ${{ secrets.YNPUT_BOT_TOKEN }}
|
||||
source_ref: 'main'
|
||||
target_branch: 'develop'
|
||||
commit_message_template: '[Automated] Merged {source_ref} into {target_branch}'
|
||||
|
|
|
|||
124
.github/workflows/release.yml
vendored
124
.github/workflows/release.yml
vendored
|
|
@ -1,124 +0,0 @@
|
|||
name: Stable Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- prereleased
|
||||
|
||||
jobs:
|
||||
create_release:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor != 'pypebot'
|
||||
|
||||
steps:
|
||||
- name: 🚛 Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.7
|
||||
- name: Install Python requirements
|
||||
run: pip install gitpython semver PyGithub
|
||||
|
||||
- name: 💉 Inject new version into files
|
||||
id: version
|
||||
run: |
|
||||
echo ::set-output name=current_version::${GITHUB_REF#refs/*/}
|
||||
RESULT=$(python ./tools/ci_tools.py --finalize ${GITHUB_REF#refs/*/})
|
||||
LASTRELEASE=$(python ./tools/ci_tools.py --lastversion release)
|
||||
|
||||
echo ::set-output name=last_release::$LASTRELEASE
|
||||
echo ::set-output name=release_tag::$RESULT
|
||||
|
||||
# - name: "✏️ Generate full changelog"
|
||||
# if: steps.version.outputs.release_tag != 'skip'
|
||||
# id: generate-full-changelog
|
||||
# uses: heinrichreimer/github-changelog-generator-action@v2.3
|
||||
# with:
|
||||
# token: ${{ secrets.ADMIN_TOKEN }}
|
||||
# addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**🔀 Refactored code**", "labels":["refactor"]}}'
|
||||
# issues: false
|
||||
# issuesWoLabels: false
|
||||
# sinceTag: "3.12.0"
|
||||
# maxIssues: 100
|
||||
# pullRequests: true
|
||||
# prWoLabels: false
|
||||
# author: false
|
||||
# unreleased: true
|
||||
# compareLink: true
|
||||
# stripGeneratorNotice: true
|
||||
# verbose: true
|
||||
# futureRelease: ${{ steps.version.outputs.release_tag }}
|
||||
# excludeTagsRegex: "CI/.+"
|
||||
# releaseBranch: "main"
|
||||
|
||||
- name: 💾 Commit and Tag
|
||||
id: git_commit
|
||||
if: steps.version.outputs.release_tag != 'skip'
|
||||
run: |
|
||||
git config user.email ${{ secrets.CI_EMAIL }}
|
||||
git config user.name ${{ secrets.CI_USER }}
|
||||
git add .
|
||||
git commit -m "[Automated] Release"
|
||||
tag_name="${{ steps.version.outputs.release_tag }}"
|
||||
git tag -a $tag_name -m "stable release"
|
||||
|
||||
- name: 🔏 Push to protected main branch
|
||||
if: steps.version.outputs.release_tag != 'skip'
|
||||
uses: CasperWA/push-protected@v2.10.0
|
||||
with:
|
||||
token: ${{ secrets.ADMIN_TOKEN }}
|
||||
branch: main
|
||||
tags: true
|
||||
unprotect_reviews: true
|
||||
|
||||
- name: "✏️ Generate last changelog"
|
||||
if: steps.version.outputs.release_tag != 'skip'
|
||||
id: generate-last-changelog
|
||||
uses: heinrichreimer/github-changelog-generator-action@v2.2
|
||||
with:
|
||||
token: ${{ secrets.ADMIN_TOKEN }}
|
||||
addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**🔀 Refactored code**", "labels":["refactor"]}}'
|
||||
issues: false
|
||||
issuesWoLabels: false
|
||||
sinceTag: ${{ steps.version.outputs.last_release }}
|
||||
maxIssues: 100
|
||||
pullRequests: true
|
||||
prWoLabels: false
|
||||
author: false
|
||||
unreleased: true
|
||||
compareLink: true
|
||||
stripGeneratorNotice: true
|
||||
verbose: true
|
||||
futureRelease: ${{ steps.version.outputs.release_tag }}
|
||||
excludeTagsRegex: "CI/.+"
|
||||
releaseBranch: "main"
|
||||
stripHeaders: true
|
||||
base: 'none'
|
||||
|
||||
|
||||
- name: 🚀 Github Release
|
||||
if: steps.version.outputs.release_tag != 'skip'
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
body: ${{ steps.generate-last-changelog.outputs.changelog }}
|
||||
tag: ${{ steps.version.outputs.release_tag }}
|
||||
token: ${{ secrets.ADMIN_TOKEN }}
|
||||
|
||||
- name: ☠ Delete Pre-release
|
||||
if: steps.version.outputs.release_tag != 'skip'
|
||||
uses: cb80/delrel@latest
|
||||
with:
|
||||
tag: "${{ steps.version.outputs.current_version }}"
|
||||
|
||||
- name: 🔁 Merge main back to develop
|
||||
if: steps.version.outputs.release_tag != 'skip'
|
||||
uses: everlytic/branch-merge@1.1.0
|
||||
with:
|
||||
github_token: ${{ secrets.ADMIN_TOKEN }}
|
||||
source_ref: 'main'
|
||||
target_branch: 'develop'
|
||||
commit_message_template: '[Automated] Merged release {source_ref} into {target_branch}'
|
||||
30
.github/workflows/test_build.yml
vendored
30
.github/workflows/test_build.yml
vendored
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.7]
|
||||
python-version: [3.9]
|
||||
|
||||
steps:
|
||||
- name: 🚛 Checkout Code
|
||||
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
|
||||
- name: 🧵 Install Requirements
|
||||
shell: pwsh
|
||||
run: |
|
||||
|
|
@ -45,7 +45,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.7]
|
||||
python-version: [3.9]
|
||||
|
||||
steps:
|
||||
- name: 🚛 Checkout Code
|
||||
|
|
@ -64,27 +64,3 @@ jobs:
|
|||
run: |
|
||||
export SKIP_THIRD_PARTY_VALIDATION="1"
|
||||
./tools/build.sh
|
||||
|
||||
# MacOS-latest:
|
||||
|
||||
# runs-on: macos-latest
|
||||
# strategy:
|
||||
# matrix:
|
||||
# python-version: [3.7]
|
||||
|
||||
# steps:
|
||||
# - name: 🚛 Checkout Code
|
||||
# uses: actions/checkout@v2
|
||||
|
||||
# - name: Set up Python
|
||||
# uses: actions/setup-python@v2
|
||||
# with:
|
||||
# python-version: ${{ matrix.python-version }}
|
||||
|
||||
# - name: 🧵 Install Requirements
|
||||
# run: |
|
||||
# ./tools/create_env.sh
|
||||
|
||||
# - name: 🔨 Build
|
||||
# run: |
|
||||
# ./tools/build.sh
|
||||
12
.pre-commit-config.yaml
Normal file
12
.pre-commit-config.yaml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
- id: no-commit-to-branch
|
||||
args: [ '--pattern', '^(?!((release|enhancement|feature|bugfix|documentation|tests|local|chore)\/[a-zA-Z0-9\-_]+)$).*' ]
|
||||
1378
CHANGELOG.md
1378
CHANGELOG.md
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
# Build Pype docker image
|
||||
FROM ubuntu:focal AS builder
|
||||
ARG OPENPYPE_PYTHON_VERSION=3.7.12
|
||||
ARG OPENPYPE_PYTHON_VERSION=3.9.12
|
||||
ARG BUILD_DATE
|
||||
ARG VERSION
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Build Pype docker image
|
||||
FROM centos:7 AS builder
|
||||
ARG OPENPYPE_PYTHON_VERSION=3.7.12
|
||||
ARG OPENPYPE_PYTHON_VERSION=3.9.12
|
||||
|
||||
LABEL org.opencontainers.image.name="pypeclub/openpype"
|
||||
LABEL org.opencontainers.image.title="OpenPype Docker Image"
|
||||
|
|
@ -96,11 +96,11 @@ RUN source $HOME/.bashrc \
|
|||
RUN source $HOME/.bashrc \
|
||||
&& bash ./tools/build.sh
|
||||
|
||||
RUN cp /usr/lib64/libffi* ./build/exe.linux-x86_64-3.7/lib \
|
||||
&& cp /usr/lib64/libssl* ./build/exe.linux-x86_64-3.7/lib \
|
||||
&& cp /usr/lib64/libcrypto* ./build/exe.linux-x86_64-3.7/lib \
|
||||
&& cp /root/.pyenv/versions/${OPENPYPE_PYTHON_VERSION}/lib/libpython* ./build/exe.linux-x86_64-3.7/lib \
|
||||
&& cp /usr/lib64/libxcb* ./build/exe.linux-x86_64-3.7/vendor/python/PySide2/Qt/lib
|
||||
RUN cp /usr/lib64/libffi* ./build/exe.linux-x86_64-3.9/lib \
|
||||
&& cp /usr/lib64/libssl* ./build/exe.linux-x86_64-3.9/lib \
|
||||
&& cp /usr/lib64/libcrypto* ./build/exe.linux-x86_64-3.9/lib \
|
||||
&& cp /root/.pyenv/versions/${OPENPYPE_PYTHON_VERSION}/lib/libpython* ./build/exe.linux-x86_64-3.9/lib \
|
||||
&& cp /usr/lib64/libxcb* ./build/exe.linux-x86_64-3.9/vendor/python/PySide2/Qt/lib
|
||||
|
||||
RUN cd /opt/openpype \
|
||||
rm -rf ./vendor/bin
|
||||
|
|
|
|||
81
Dockerfile.debian
Normal file
81
Dockerfile.debian
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# Build Pype docker image
|
||||
FROM debian:bullseye AS builder
|
||||
ARG OPENPYPE_PYTHON_VERSION=3.9.12
|
||||
ARG BUILD_DATE
|
||||
ARG VERSION
|
||||
|
||||
LABEL maintainer="info@openpype.io"
|
||||
LABEL description="Docker Image to build and run OpenPype under Ubuntu 20.04"
|
||||
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/OpenPype"
|
||||
LABEL org.opencontainers.image.documentation="https://openpype.io/docs/system_introduction"
|
||||
LABEL org.opencontainers.image.created=$BUILD_DATE
|
||||
LABEL org.opencontainers.image.version=$VERSION
|
||||
|
||||
USER root
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# update base
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
bash \
|
||||
git \
|
||||
cmake \
|
||||
make \
|
||||
curl \
|
||||
wget \
|
||||
build-essential \
|
||||
libssl-dev \
|
||||
zlib1g-dev \
|
||||
libbz2-dev \
|
||||
libreadline-dev \
|
||||
libsqlite3-dev \
|
||||
llvm \
|
||||
libncursesw5-dev \
|
||||
xz-utils \
|
||||
tk-dev \
|
||||
libxml2-dev \
|
||||
libxmlsec1-dev \
|
||||
libffi-dev \
|
||||
liblzma-dev \
|
||||
patchelf
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
|
||||
RUN mkdir /opt/openpype
|
||||
|
||||
# download and install pyenv
|
||||
RUN curl https://pyenv.run | bash \
|
||||
&& echo 'export PATH="$HOME/.pyenv/bin:$PATH"'>> $HOME/init_pyenv.sh \
|
||||
&& echo 'eval "$(pyenv init -)"' >> $HOME/init_pyenv.sh \
|
||||
&& echo 'eval "$(pyenv virtualenv-init -)"' >> $HOME/init_pyenv.sh \
|
||||
&& echo 'eval "$(pyenv init --path)"' >> $HOME/init_pyenv.sh
|
||||
|
||||
# install python with pyenv
|
||||
RUN source $HOME/init_pyenv.sh \
|
||||
&& pyenv install ${OPENPYPE_PYTHON_VERSION}
|
||||
|
||||
COPY . /opt/openpype/
|
||||
|
||||
RUN chmod +x /opt/openpype/tools/create_env.sh && chmod +x /opt/openpype/tools/build.sh
|
||||
|
||||
WORKDIR /opt/openpype
|
||||
|
||||
# set local python version
|
||||
RUN cd /opt/openpype \
|
||||
&& source $HOME/init_pyenv.sh \
|
||||
&& pyenv local ${OPENPYPE_PYTHON_VERSION}
|
||||
|
||||
# fetch third party tools/libraries
|
||||
RUN source $HOME/init_pyenv.sh \
|
||||
&& ./tools/create_env.sh \
|
||||
&& ./tools/fetch_thirdparty_libs.sh
|
||||
|
||||
# build openpype
|
||||
RUN source $HOME/init_pyenv.sh \
|
||||
&& bash ./tools/build.sh
|
||||
199
HISTORY.md
199
HISTORY.md
|
|
@ -1,5 +1,204 @@
|
|||
# Changelog
|
||||
|
||||
## [3.15.0](https://github.com/ynput/OpenPype/tree/3.15.0)
|
||||
|
||||
[Full Changelog](https://github.com/ynput/OpenPype/compare/3.14.10...3.15.0)
|
||||
|
||||
**Deprecated:**
|
||||
|
||||
- General: Fill default values of new publish template profiles [\#4245](https://github.com/ynput/OpenPype/pull/4245)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- documentation: Split tools into separate entries [\#4342](https://github.com/ynput/OpenPype/pull/4342)
|
||||
- Documentation: Fix harmony docs [\#4301](https://github.com/ynput/OpenPype/pull/4301)
|
||||
- Remove staging logic set by OpenPype version [\#3979](https://github.com/ynput/OpenPype/pull/3979)
|
||||
|
||||
**🆕 New features**
|
||||
|
||||
- General: Push to studio library [\#4284](https://github.com/ynput/OpenPype/pull/4284)
|
||||
- Colorspace Management and Distribution [\#4195](https://github.com/ynput/OpenPype/pull/4195)
|
||||
- Nuke: refactor to latest publisher workfow [\#4006](https://github.com/ynput/OpenPype/pull/4006)
|
||||
- Update to Python 3.9 [\#3546](https://github.com/ynput/OpenPype/pull/3546)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
- Unreal: Don't use mongo queries in 'ExistingLayoutLoader' [\#4356](https://github.com/ynput/OpenPype/pull/4356)
|
||||
- General: Loader and Creator plugins can be disabled [\#4310](https://github.com/ynput/OpenPype/pull/4310)
|
||||
- General: Unbind poetry version [\#4306](https://github.com/ynput/OpenPype/pull/4306)
|
||||
- General: Enhanced enum def items [\#4295](https://github.com/ynput/OpenPype/pull/4295)
|
||||
- Git: add pre-commit hooks [\#4289](https://github.com/ynput/OpenPype/pull/4289)
|
||||
- Tray Publisher: Improve Online family functionality [\#4263](https://github.com/ynput/OpenPype/pull/4263)
|
||||
- General: Update MacOs to PySide6 [\#4255](https://github.com/ynput/OpenPype/pull/4255)
|
||||
- Build: update to Gazu in toml [\#4208](https://github.com/ynput/OpenPype/pull/4208)
|
||||
- Global: adding imageio to settings [\#4158](https://github.com/ynput/OpenPype/pull/4158)
|
||||
- Blender: added project settings for validator no colons in name [\#4149](https://github.com/ynput/OpenPype/pull/4149)
|
||||
- Dockerfile for Debian Bullseye [\#4108](https://github.com/ynput/OpenPype/pull/4108)
|
||||
- AfterEffects: publish multiple compositions [\#4092](https://github.com/ynput/OpenPype/pull/4092)
|
||||
- AfterEffects: make new publisher default [\#4056](https://github.com/ynput/OpenPype/pull/4056)
|
||||
- Photoshop: make new publisher default [\#4051](https://github.com/ynput/OpenPype/pull/4051)
|
||||
- Feature/multiverse [\#4046](https://github.com/ynput/OpenPype/pull/4046)
|
||||
- Tests: add support for deadline for automatic tests [\#3989](https://github.com/ynput/OpenPype/pull/3989)
|
||||
- Add version to shortcut name [\#3906](https://github.com/ynput/OpenPype/pull/3906)
|
||||
- TrayPublisher: Removed from experimental tools [\#3667](https://github.com/ynput/OpenPype/pull/3667)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- change 3.7 to 3.9 in folder name [\#4354](https://github.com/ynput/OpenPype/pull/4354)
|
||||
- PushToProject: Fix hierarchy of project change [\#4350](https://github.com/ynput/OpenPype/pull/4350)
|
||||
- Fix photoshop workfile save-as [\#4347](https://github.com/ynput/OpenPype/pull/4347)
|
||||
- Nuke Input process node sourcing improvements [\#4341](https://github.com/ynput/OpenPype/pull/4341)
|
||||
- New publisher: Some validation plugin tweaks [\#4339](https://github.com/ynput/OpenPype/pull/4339)
|
||||
- Harmony: fix unable to change workfile on Mac [\#4334](https://github.com/ynput/OpenPype/pull/4334)
|
||||
- Global: fixing in-place source publishing for editorial [\#4333](https://github.com/ynput/OpenPype/pull/4333)
|
||||
- General: Use class constants of QMessageBox [\#4332](https://github.com/ynput/OpenPype/pull/4332)
|
||||
- TVPaint: Fix plugin for TVPaint 11.7 [\#4328](https://github.com/ynput/OpenPype/pull/4328)
|
||||
- Exctract OTIO review has improved quality [\#4325](https://github.com/ynput/OpenPype/pull/4325)
|
||||
- Ftrack: fix typos causing bugs in sync [\#4322](https://github.com/ynput/OpenPype/pull/4322)
|
||||
- General: Python 2 compatibility of instance collector [\#4320](https://github.com/ynput/OpenPype/pull/4320)
|
||||
- Slack: user groups speedup [\#4318](https://github.com/ynput/OpenPype/pull/4318)
|
||||
- Maya: Bug - Multiverse extractor executed on plain animation family [\#4315](https://github.com/ynput/OpenPype/pull/4315)
|
||||
- Fix run\_documentation.ps1 [\#4312](https://github.com/ynput/OpenPype/pull/4312)
|
||||
- Nuke: new creators fixes [\#4308](https://github.com/ynput/OpenPype/pull/4308)
|
||||
- General: missing comment on standalone and tray publisher [\#4303](https://github.com/ynput/OpenPype/pull/4303)
|
||||
- AfterEffects: Fix for audio from mp4 layer [\#4296](https://github.com/ynput/OpenPype/pull/4296)
|
||||
- General: Update gazu in poetry lock [\#4247](https://github.com/ynput/OpenPype/pull/4247)
|
||||
- Bug: Fixing version detection and filtering in Igniter [\#3914](https://github.com/ynput/OpenPype/pull/3914)
|
||||
- Bug: Create missing version dir [\#3903](https://github.com/ynput/OpenPype/pull/3903)
|
||||
|
||||
**🔀 Refactored code**
|
||||
|
||||
- Remove redundant export\_alembic method. [\#4293](https://github.com/ynput/OpenPype/pull/4293)
|
||||
- Igniter: Use qtpy modules instead of Qt [\#4237](https://github.com/ynput/OpenPype/pull/4237)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Sort families by alphabetical order in the Create plugin [\#4346](https://github.com/ynput/OpenPype/pull/4346)
|
||||
- Global: Validate unique subsets [\#4336](https://github.com/ynput/OpenPype/pull/4336)
|
||||
- Maya: Collect instances preserve handles even if frameStart + frameEnd matches context [\#3437](https://github.com/ynput/OpenPype/pull/3437)
|
||||
|
||||
|
||||
## [3.14.10](https://github.com/ynput/OpenPype/tree/3.14.10)
|
||||
|
||||
[Full Changelog](https://github.com/ynput/OpenPype/compare/3.14.9...3.14.10)
|
||||
|
||||
**🆕 New features**
|
||||
|
||||
- Global | Nuke: Creator placeholders in workfile template builder [\#4266](https://github.com/ynput/OpenPype/pull/4266)
|
||||
- Slack: Added dynamic message [\#4265](https://github.com/ynput/OpenPype/pull/4265)
|
||||
- Blender: Workfile Loader [\#4234](https://github.com/ynput/OpenPype/pull/4234)
|
||||
- Unreal: Publishing and Loading for UAssets [\#4198](https://github.com/ynput/OpenPype/pull/4198)
|
||||
- Publish: register publishes without copying them [\#4157](https://github.com/ynput/OpenPype/pull/4157)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
- General: Added install method with docstring to HostBase [\#4298](https://github.com/ynput/OpenPype/pull/4298)
|
||||
- Traypublisher: simple editorial multiple edl [\#4248](https://github.com/ynput/OpenPype/pull/4248)
|
||||
- General: Extend 'IPluginPaths' to have more available methods [\#4214](https://github.com/ynput/OpenPype/pull/4214)
|
||||
- Refactorization of folder coloring [\#4211](https://github.com/ynput/OpenPype/pull/4211)
|
||||
- Flame - loading multilayer with controlled layer names [\#4204](https://github.com/ynput/OpenPype/pull/4204)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Unreal: fix missing `maintained_selection` call [\#4300](https://github.com/ynput/OpenPype/pull/4300)
|
||||
- Ftrack: Fix receive of host ip on MacOs [\#4288](https://github.com/ynput/OpenPype/pull/4288)
|
||||
- SiteSync: sftp connection failing when shouldnt be tested [\#4278](https://github.com/ynput/OpenPype/pull/4278)
|
||||
- Deadline: fix default value for passing mongo url [\#4275](https://github.com/ynput/OpenPype/pull/4275)
|
||||
- Scene Manager: Fix variable name [\#4268](https://github.com/ynput/OpenPype/pull/4268)
|
||||
- Slack: notification fails because of missing published path [\#4264](https://github.com/ynput/OpenPype/pull/4264)
|
||||
- hiero: creator gui with min max [\#4257](https://github.com/ynput/OpenPype/pull/4257)
|
||||
- NiceCheckbox: Fix checker positioning in Python 2 [\#4253](https://github.com/ynput/OpenPype/pull/4253)
|
||||
- Publisher: Fix 'CreatorType' not equal for Python 2 DCCs [\#4249](https://github.com/ynput/OpenPype/pull/4249)
|
||||
- Deadline: fix dependencies [\#4242](https://github.com/ynput/OpenPype/pull/4242)
|
||||
- Houdini: hotfix instance data access [\#4236](https://github.com/ynput/OpenPype/pull/4236)
|
||||
- bugfix/image plane load error [\#4222](https://github.com/ynput/OpenPype/pull/4222)
|
||||
- Hiero: thumbnail from multilayer exr [\#4209](https://github.com/ynput/OpenPype/pull/4209)
|
||||
|
||||
**🔀 Refactored code**
|
||||
|
||||
- Resolve: Use qtpy in Resolve [\#4254](https://github.com/ynput/OpenPype/pull/4254)
|
||||
- Houdini: Use qtpy in Houdini [\#4252](https://github.com/ynput/OpenPype/pull/4252)
|
||||
- Max: Use qtpy in Max [\#4251](https://github.com/ynput/OpenPype/pull/4251)
|
||||
- Maya: Use qtpy in Maya [\#4250](https://github.com/ynput/OpenPype/pull/4250)
|
||||
- Hiero: Use qtpy in Hiero [\#4240](https://github.com/ynput/OpenPype/pull/4240)
|
||||
- Nuke: Use qtpy in Nuke [\#4239](https://github.com/ynput/OpenPype/pull/4239)
|
||||
- Flame: Use qtpy in flame [\#4238](https://github.com/ynput/OpenPype/pull/4238)
|
||||
- General: Legacy io not used in global plugins [\#4134](https://github.com/ynput/OpenPype/pull/4134)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Bump json5 from 1.0.1 to 1.0.2 in /website [\#4292](https://github.com/ynput/OpenPype/pull/4292)
|
||||
- Maya: Fix validate frame range repair + fix create render with deadline disabled [\#4279](https://github.com/ynput/OpenPype/pull/4279)
|
||||
|
||||
|
||||
## [3.14.9](https://github.com/pypeclub/OpenPype/tree/3.14.9)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.8...3.14.9)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Documentation: Testing on Deadline [\#4185](https://github.com/pypeclub/OpenPype/pull/4185)
|
||||
- Consistent Python version [\#4160](https://github.com/pypeclub/OpenPype/pull/4160)
|
||||
|
||||
**🆕 New features**
|
||||
|
||||
- Feature/op 4397 gl tf extractor for maya [\#4192](https://github.com/pypeclub/OpenPype/pull/4192)
|
||||
- Maya: Extractor for Unreal SkeletalMesh [\#4174](https://github.com/pypeclub/OpenPype/pull/4174)
|
||||
- 3dsmax: integration [\#4168](https://github.com/pypeclub/OpenPype/pull/4168)
|
||||
- Blender: Extract Alembic Animations [\#4128](https://github.com/pypeclub/OpenPype/pull/4128)
|
||||
- Unreal: Load Alembic Animations [\#4127](https://github.com/pypeclub/OpenPype/pull/4127)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
- Houdini: Use new interface class name for publish host [\#4220](https://github.com/pypeclub/OpenPype/pull/4220)
|
||||
- General: Default command for headless mode is interactive [\#4203](https://github.com/pypeclub/OpenPype/pull/4203)
|
||||
- Maya: Enhanced ASS publishing [\#4196](https://github.com/pypeclub/OpenPype/pull/4196)
|
||||
- Feature/op 3924 implement ass extractor [\#4188](https://github.com/pypeclub/OpenPype/pull/4188)
|
||||
- File transactions: Source path is destination path [\#4184](https://github.com/pypeclub/OpenPype/pull/4184)
|
||||
- Deadline: improve environment processing [\#4182](https://github.com/pypeclub/OpenPype/pull/4182)
|
||||
- General: Comment per instance in Publisher [\#4178](https://github.com/pypeclub/OpenPype/pull/4178)
|
||||
- Ensure Mongo database directory exists in Windows. [\#4166](https://github.com/pypeclub/OpenPype/pull/4166)
|
||||
- Note about unrestricted execution on Windows. [\#4161](https://github.com/pypeclub/OpenPype/pull/4161)
|
||||
- Maya: Enable thumbnail transparency on extraction. [\#4147](https://github.com/pypeclub/OpenPype/pull/4147)
|
||||
- Maya: Disable viewport Pan/Zoom on playblast extraction. [\#4146](https://github.com/pypeclub/OpenPype/pull/4146)
|
||||
- Maya: Optional viewport refresh on pointcache extraction [\#4144](https://github.com/pypeclub/OpenPype/pull/4144)
|
||||
- CelAction: refactory integration to current openpype [\#4140](https://github.com/pypeclub/OpenPype/pull/4140)
|
||||
- Maya: create and publish bounding box geometry [\#4131](https://github.com/pypeclub/OpenPype/pull/4131)
|
||||
- Changed the UOpenPypePublishInstance to use the UDataAsset class [\#4124](https://github.com/pypeclub/OpenPype/pull/4124)
|
||||
- General: Collection Audio speed up [\#4110](https://github.com/pypeclub/OpenPype/pull/4110)
|
||||
- Maya: keep existing AOVs when creating render instance [\#4087](https://github.com/pypeclub/OpenPype/pull/4087)
|
||||
- General: Oiio conversion multipart fix [\#4060](https://github.com/pypeclub/OpenPype/pull/4060)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Publisher: Signal type issues in Python 2 DCCs [\#4230](https://github.com/pypeclub/OpenPype/pull/4230)
|
||||
- Blender: Fix Layout Family Versioning [\#4228](https://github.com/pypeclub/OpenPype/pull/4228)
|
||||
- Blender: Fix Create Camera "Use selection" [\#4226](https://github.com/pypeclub/OpenPype/pull/4226)
|
||||
- TrayPublisher - join needs list [\#4224](https://github.com/pypeclub/OpenPype/pull/4224)
|
||||
- General: Event callbacks pass event to callbacks as expected [\#4210](https://github.com/pypeclub/OpenPype/pull/4210)
|
||||
- Build:Revert .toml update of Gazu [\#4207](https://github.com/pypeclub/OpenPype/pull/4207)
|
||||
- Nuke: fixed imageio node overrides subset filter [\#4202](https://github.com/pypeclub/OpenPype/pull/4202)
|
||||
- Maya: pointcache [\#4201](https://github.com/pypeclub/OpenPype/pull/4201)
|
||||
- Unreal: Support for Unreal Engine 5.1 [\#4199](https://github.com/pypeclub/OpenPype/pull/4199)
|
||||
- General: Integrate thumbnail looks for thumbnail to multiple places [\#4181](https://github.com/pypeclub/OpenPype/pull/4181)
|
||||
- Various minor bugfixes [\#4172](https://github.com/pypeclub/OpenPype/pull/4172)
|
||||
- Nuke/Hiero: Remove tkinter library paths before launch [\#4171](https://github.com/pypeclub/OpenPype/pull/4171)
|
||||
- Flame: vertical alignment of layers [\#4169](https://github.com/pypeclub/OpenPype/pull/4169)
|
||||
- Nuke: correct detection of viewer and display [\#4165](https://github.com/pypeclub/OpenPype/pull/4165)
|
||||
- Settings UI: Don't create QApplication if already exists [\#4156](https://github.com/pypeclub/OpenPype/pull/4156)
|
||||
- General: Extract review handle start offset of sequences [\#4152](https://github.com/pypeclub/OpenPype/pull/4152)
|
||||
- Maya: Maintain time connections on Alembic update. [\#4143](https://github.com/pypeclub/OpenPype/pull/4143)
|
||||
|
||||
**🔀 Refactored code**
|
||||
|
||||
- General: Use qtpy in modules and hosts UIs which are running in OpenPype process [\#4225](https://github.com/pypeclub/OpenPype/pull/4225)
|
||||
- Tools: Use qtpy instead of Qt in standalone tools [\#4223](https://github.com/pypeclub/OpenPype/pull/4223)
|
||||
- General: Use qtpy in settings UI [\#4215](https://github.com/pypeclub/OpenPype/pull/4215)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- layout publish more than one container issue [\#4098](https://github.com/pypeclub/OpenPype/pull/4098)
|
||||
|
||||
## [3.14.8](https://github.com/pypeclub/OpenPype/tree/3.14.8)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.7...3.14.8)
|
||||
|
|
|
|||
32
README.md
32
README.md
|
|
@ -5,8 +5,7 @@
|
|||
OpenPype
|
||||
====
|
||||
|
||||
[](https://github.com/pypeclub/pype/actions/workflows/documentation.yml) 
|
||||
|
||||
[](https://github.com/pypeclub/pype/actions/workflows/documentation.yml) 
|
||||
|
||||
|
||||
Introduction
|
||||
|
|
@ -31,7 +30,7 @@ The main things you will need to run and build OpenPype are:
|
|||
- **Terminal** in your OS
|
||||
- PowerShell 5.0+ (Windows)
|
||||
- Bash (Linux)
|
||||
- [**Python 3.7.8**](#python) or higher
|
||||
- [**Python 3.9.6**](#python) or higher
|
||||
- [**MongoDB**](#database) (needed only for local development)
|
||||
|
||||
|
||||
|
|
@ -50,13 +49,14 @@ For more details on requirements visit [requirements documentation](https://open
|
|||
Building OpenPype
|
||||
-------------
|
||||
|
||||
To build OpenPype you currently need [Python 3.7](https://www.python.org/downloads/) as we are following
|
||||
To build OpenPype you currently need [Python 3.9](https://www.python.org/downloads/) as we are following
|
||||
[vfx platform](https://vfxplatform.com). Because of some Linux distros comes with newer Python version
|
||||
already, you need to install **3.7** version and make use of it. You can use perhaps [pyenv](https://github.com/pyenv/pyenv) for this on Linux.
|
||||
already, you need to install **3.9** version and make use of it. You can use perhaps [pyenv](https://github.com/pyenv/pyenv) for this on Linux.
|
||||
**Note**: We do not support 3.9.0 because of [this bug](https://github.com/python/cpython/pull/22670). Please, use higher versions of 3.9.x.
|
||||
|
||||
### Windows
|
||||
|
||||
You will need [Python 3.7](https://www.python.org/downloads/) and [git](https://git-scm.com/downloads).
|
||||
You will need [Python >= 3.9.1](https://www.python.org/downloads/) and [git](https://git-scm.com/downloads).
|
||||
More tools might be needed for installing dependencies (for example for **OpenTimelineIO**) - mostly
|
||||
development tools like [CMake](https://cmake.org/) and [Visual Studio](https://visualstudio.microsoft.com/cs/downloads/)
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ OpenPype is build using [CX_Freeze](https://cx-freeze.readthedocs.io/en/latest)
|
|||
|
||||
### macOS
|
||||
|
||||
You will need [Python 3.7](https://www.python.org/downloads/) and [git](https://git-scm.com/downloads). You'll need also other tools to build
|
||||
You will need [Python >= 3.9](https://www.python.org/downloads/) and [git](https://git-scm.com/downloads). You'll need also other tools to build
|
||||
some OpenPype dependencies like [CMake](https://cmake.org/) and **XCode Command Line Tools** (or some other build system).
|
||||
|
||||
Easy way of installing everything necessary is to use [Homebrew](https://brew.sh):
|
||||
|
|
@ -106,19 +106,19 @@ exec "$SHELL"
|
|||
PATH=$(pyenv root)/shims:$PATH
|
||||
```
|
||||
|
||||
4) Pull in required Python version 3.7.x
|
||||
4) Pull in required Python version 3.9.x
|
||||
```sh
|
||||
# install Python build dependences
|
||||
brew install openssl readline sqlite3 xz zlib
|
||||
|
||||
# replace with up-to-date 3.7.x version
|
||||
pyenv install 3.7.9
|
||||
# replace with up-to-date 3.9.x version
|
||||
pyenv install 3.9.6
|
||||
```
|
||||
|
||||
5) Set local Python version
|
||||
```sh
|
||||
# switch to OpenPype source directory
|
||||
pyenv local 3.7.9
|
||||
pyenv local 3.9.6
|
||||
```
|
||||
|
||||
#### To build OpenPype:
|
||||
|
|
@ -145,7 +145,7 @@ sudo ./tools/docker_build.sh centos7
|
|||
If all is successful, you'll find built OpenPype in `./build/` folder.
|
||||
|
||||
#### Manual build
|
||||
You will need [Python 3.7](https://www.python.org/downloads/) and [git](https://git-scm.com/downloads). You'll also need [curl](https://curl.se) on systems that doesn't have one preinstalled.
|
||||
You will need [Python >= 3.9](https://www.python.org/downloads/) and [git](https://git-scm.com/downloads). You'll also need [curl](https://curl.se) on systems that doesn't have one preinstalled.
|
||||
|
||||
To build Python related stuff, you need Python header files installed (`python3-dev` on Ubuntu for example).
|
||||
|
||||
|
|
@ -222,14 +222,14 @@ eval "$(pyenv virtualenv-init -)"
|
|||
# reload shell
|
||||
exec $SHELL
|
||||
|
||||
# install Python 3.7.9
|
||||
pyenv install -v 3.7.9
|
||||
# install Python 3.9.x
|
||||
pyenv install -v 3.9.6
|
||||
|
||||
# change path to OpenPype 3
|
||||
cd /path/to/openpype-3
|
||||
|
||||
# set local python version
|
||||
pyenv local 3.7.9
|
||||
pyenv local 3.9.6
|
||||
|
||||
```
|
||||
</details>
|
||||
|
|
@ -345,4 +345,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import enlighten
|
||||
import os
|
||||
import re
|
||||
import urllib
|
||||
|
|
@ -252,6 +251,11 @@ class RemoteFileHandler:
|
|||
if key.startswith('download_warning'):
|
||||
return value
|
||||
|
||||
# handle antivirus warning for big zips
|
||||
found = re.search("(confirm=)([^&.+])", response.text)
|
||||
if found:
|
||||
return found.groups()[1]
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -259,15 +263,9 @@ class RemoteFileHandler:
|
|||
response_gen, destination,
|
||||
):
|
||||
with open(destination, "wb") as f:
|
||||
pbar = enlighten.Counter(
|
||||
total=None, desc="Save content", units="%", color="green")
|
||||
progress = 0
|
||||
for chunk in response_gen:
|
||||
if chunk: # filter out keep-alive new chunks
|
||||
f.write(chunk)
|
||||
progress += len(chunk)
|
||||
|
||||
pbar.close()
|
||||
|
||||
@staticmethod
|
||||
def _quota_exceeded(first_chunk):
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ def open_dialog():
|
|||
if os.getenv("OPENPYPE_HEADLESS_MODE"):
|
||||
print("!!! Can't open dialog in headless mode. Exiting.")
|
||||
sys.exit(1)
|
||||
from Qt import QtWidgets, QtCore
|
||||
from qtpy import QtWidgets, QtCore
|
||||
from .install_dialog import InstallDialog
|
||||
|
||||
scale_attr = getattr(QtCore.Qt, "AA_EnableHighDpiScaling", None)
|
||||
|
|
@ -47,7 +47,7 @@ def open_update_window(openpype_version):
|
|||
if os.getenv("OPENPYPE_HEADLESS_MODE"):
|
||||
print("!!! Can't open dialog in headless mode. Exiting.")
|
||||
sys.exit(1)
|
||||
from Qt import QtWidgets, QtCore
|
||||
from qtpy import QtWidgets, QtCore
|
||||
from .update_window import UpdateWindow
|
||||
|
||||
scale_attr = getattr(QtCore.Qt, "AA_EnableHighDpiScaling", None)
|
||||
|
|
@ -71,7 +71,7 @@ def show_message_dialog(title, message):
|
|||
if os.getenv("OPENPYPE_HEADLESS_MODE"):
|
||||
print("!!! Can't open dialog in headless mode. Exiting.")
|
||||
sys.exit(1)
|
||||
from Qt import QtWidgets, QtCore
|
||||
from qtpy import QtWidgets, QtCore
|
||||
from .message_dialog import MessageDialog
|
||||
|
||||
scale_attr = getattr(QtCore.Qt, "AA_EnableHighDpiScaling", None)
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
"""Open install dialog."""
|
||||
|
||||
import sys
|
||||
from Qt import QtWidgets # noqa
|
||||
from Qt.QtCore import Signal # noqa
|
||||
from qtpy import QtWidgets
|
||||
|
||||
from .install_dialog import InstallDialog
|
||||
|
||||
|
|
|
|||
|
|
@ -57,11 +57,9 @@ class OpenPypeVersion(semver.VersionInfo):
|
|||
"""Class for storing information about OpenPype version.
|
||||
|
||||
Attributes:
|
||||
staging (bool): True if it is staging version
|
||||
path (str): path to OpenPype
|
||||
|
||||
"""
|
||||
staging = False
|
||||
path = None
|
||||
# this should match any string complying with https://semver.org/
|
||||
_VERSION_REGEX = re.compile(r"(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>[a-zA-Z\d\-.]*))?(?:\+(?P<buildmetadata>[a-zA-Z\d\-.]*))?") # noqa: E501
|
||||
|
|
@ -83,12 +81,10 @@ class OpenPypeVersion(semver.VersionInfo):
|
|||
build (str): an optional build string
|
||||
version (str): if set, it will be parsed and will override
|
||||
parameters like `major`, `minor` and so on.
|
||||
staging (bool): set to True if version is staging.
|
||||
path (Path): path to version location.
|
||||
|
||||
"""
|
||||
self.path = None
|
||||
self.staging = False
|
||||
|
||||
if "version" in kwargs.keys():
|
||||
if not kwargs.get("version"):
|
||||
|
|
@ -113,29 +109,8 @@ class OpenPypeVersion(semver.VersionInfo):
|
|||
if "path" in kwargs.keys():
|
||||
kwargs.pop("path")
|
||||
|
||||
if kwargs.get("staging"):
|
||||
self.staging = kwargs.get("staging", False)
|
||||
kwargs.pop("staging")
|
||||
|
||||
if "staging" in kwargs.keys():
|
||||
kwargs.pop("staging")
|
||||
|
||||
if self.staging:
|
||||
if kwargs.get("build"):
|
||||
if "staging" not in kwargs.get("build"):
|
||||
kwargs["build"] = f"{kwargs.get('build')}-staging"
|
||||
else:
|
||||
kwargs["build"] = "staging"
|
||||
|
||||
if kwargs.get("build") and "staging" in kwargs.get("build", ""):
|
||||
self.staging = True
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __eq__(self, other):
|
||||
result = super().__eq__(other)
|
||||
return bool(result and self.staging == other.staging)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__}: {str(self)} - path={self.path}>"
|
||||
|
||||
|
|
@ -150,43 +125,11 @@ class OpenPypeVersion(semver.VersionInfo):
|
|||
return True
|
||||
|
||||
if self.finalize_version() == other.finalize_version() and \
|
||||
self.prerelease == other.prerelease and \
|
||||
self.is_staging() and not other.is_staging():
|
||||
self.prerelease == other.prerelease:
|
||||
return True
|
||||
|
||||
return result
|
||||
|
||||
def set_staging(self) -> OpenPypeVersion:
|
||||
"""Set version as staging and return it.
|
||||
|
||||
This will preserve current one.
|
||||
|
||||
Returns:
|
||||
OpenPypeVersion: Set as staging.
|
||||
|
||||
"""
|
||||
if self.staging:
|
||||
return self
|
||||
return self.replace(parts={"build": f"{self.build}-staging"})
|
||||
|
||||
def set_production(self) -> OpenPypeVersion:
|
||||
"""Set version as production and return it.
|
||||
|
||||
This will preserve current one.
|
||||
|
||||
Returns:
|
||||
OpenPypeVersion: Set as production.
|
||||
|
||||
"""
|
||||
if not self.staging:
|
||||
return self
|
||||
return self.replace(
|
||||
parts={"build": self.build.replace("-staging", "")})
|
||||
|
||||
def is_staging(self) -> bool:
|
||||
"""Test if current version is staging one."""
|
||||
return self.staging
|
||||
|
||||
def get_main_version(self) -> str:
|
||||
"""Return main version component.
|
||||
|
||||
|
|
@ -218,21 +161,8 @@ class OpenPypeVersion(semver.VersionInfo):
|
|||
if not m:
|
||||
return None
|
||||
version = OpenPypeVersion.parse(string[m.start():m.end()])
|
||||
if "staging" in string[m.start():m.end()]:
|
||||
version.staging = True
|
||||
return version
|
||||
|
||||
@classmethod
|
||||
def parse(cls, version):
|
||||
"""Extends parse to handle ta handle staging variant."""
|
||||
v = super().parse(version)
|
||||
openpype_version = cls(major=v.major, minor=v.minor,
|
||||
patch=v.patch, prerelease=v.prerelease,
|
||||
build=v.build)
|
||||
if v.build and "staging" in v.build:
|
||||
openpype_version.staging = True
|
||||
return openpype_version
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.path) if self.path else hash(str(self))
|
||||
|
||||
|
|
@ -382,80 +312,28 @@ class OpenPypeVersion(semver.VersionInfo):
|
|||
return False
|
||||
|
||||
@classmethod
|
||||
def get_local_versions(
|
||||
cls, production: bool = None,
|
||||
staging: bool = None
|
||||
) -> List:
|
||||
def get_local_versions(cls) -> List:
|
||||
"""Get all versions available on this machine.
|
||||
|
||||
Arguments give ability to specify if filtering is needed. If both
|
||||
arguments are set to None all found versions are returned.
|
||||
|
||||
Args:
|
||||
production (bool): Return production versions.
|
||||
staging (bool): Return staging versions.
|
||||
|
||||
Returns:
|
||||
list: of compatible versions available on the machine.
|
||||
|
||||
"""
|
||||
# Return all local versions if arguments are set to None
|
||||
if production is None and staging is None:
|
||||
production = True
|
||||
staging = True
|
||||
|
||||
elif production is None and not staging:
|
||||
production = True
|
||||
|
||||
elif staging is None and not production:
|
||||
staging = True
|
||||
|
||||
# Just return empty output if both are disabled
|
||||
if not production and not staging:
|
||||
return []
|
||||
|
||||
# DEPRECATED: backwards compatible way to look for versions in root
|
||||
dir_to_search = Path(user_data_dir("openpype", "pypeclub"))
|
||||
versions = OpenPypeVersion.get_versions_from_directory(dir_to_search)
|
||||
|
||||
filtered_versions = []
|
||||
for version in versions:
|
||||
if version.is_staging():
|
||||
if staging:
|
||||
filtered_versions.append(version)
|
||||
elif production:
|
||||
filtered_versions.append(version)
|
||||
return list(sorted(set(filtered_versions)))
|
||||
return list(sorted(set(versions)))
|
||||
|
||||
@classmethod
|
||||
def get_remote_versions(
|
||||
cls, production: bool = None,
|
||||
staging: bool = None
|
||||
) -> List:
|
||||
def get_remote_versions(cls) -> List:
|
||||
"""Get all versions available in OpenPype Path.
|
||||
|
||||
Arguments give ability to specify if filtering is needed. If both
|
||||
arguments are set to None all found versions are returned.
|
||||
|
||||
Args:
|
||||
production (bool): Return production versions.
|
||||
staging (bool): Return staging versions.
|
||||
Returns:
|
||||
list of OpenPypeVersions: Versions found in OpenPype path.
|
||||
|
||||
"""
|
||||
# Return all local versions if arguments are set to None
|
||||
if production is None and staging is None:
|
||||
production = True
|
||||
staging = True
|
||||
|
||||
elif production is None and not staging:
|
||||
production = True
|
||||
|
||||
elif staging is None and not production:
|
||||
staging = True
|
||||
|
||||
# Just return empty output if both are disabled
|
||||
if not production and not staging:
|
||||
return []
|
||||
|
||||
dir_to_search = None
|
||||
if cls.openpype_path_is_accessible():
|
||||
|
|
@ -476,14 +354,7 @@ class OpenPypeVersion(semver.VersionInfo):
|
|||
|
||||
versions = cls.get_versions_from_directory(dir_to_search)
|
||||
|
||||
filtered_versions = []
|
||||
for version in versions:
|
||||
if version.is_staging():
|
||||
if staging:
|
||||
filtered_versions.append(version)
|
||||
elif production:
|
||||
filtered_versions.append(version)
|
||||
return list(sorted(set(filtered_versions)))
|
||||
return list(sorted(set(versions)))
|
||||
|
||||
@staticmethod
|
||||
def get_versions_from_directory(
|
||||
|
|
@ -562,7 +433,6 @@ class OpenPypeVersion(semver.VersionInfo):
|
|||
|
||||
@staticmethod
|
||||
def get_latest_version(
|
||||
staging: bool = False,
|
||||
local: bool = None,
|
||||
remote: bool = None
|
||||
) -> Union[OpenPypeVersion, None]:
|
||||
|
|
@ -571,7 +441,6 @@ class OpenPypeVersion(semver.VersionInfo):
|
|||
The version does not contain information about path and source.
|
||||
|
||||
This is utility version to get the latest version from all found.
|
||||
Build version is not listed if staging is enabled.
|
||||
|
||||
Arguments 'local' and 'remote' define if local and remote repository
|
||||
versions are used. All versions are used if both are not set (or set
|
||||
|
|
@ -580,7 +449,6 @@ class OpenPypeVersion(semver.VersionInfo):
|
|||
'False' in that case only build version can be used.
|
||||
|
||||
Args:
|
||||
staging (bool, optional): List staging versions if True.
|
||||
local (bool, optional): List local versions if True.
|
||||
remote (bool, optional): List remote versions if True.
|
||||
|
||||
|
|
@ -599,22 +467,9 @@ class OpenPypeVersion(semver.VersionInfo):
|
|||
remote = True
|
||||
|
||||
installed_version = OpenPypeVersion.get_installed_version()
|
||||
local_versions = []
|
||||
remote_versions = []
|
||||
if local:
|
||||
local_versions = OpenPypeVersion.get_local_versions(
|
||||
staging=staging
|
||||
)
|
||||
if remote:
|
||||
remote_versions = OpenPypeVersion.get_remote_versions(
|
||||
staging=staging
|
||||
)
|
||||
all_versions = local_versions + remote_versions
|
||||
if not staging:
|
||||
all_versions.append(installed_version)
|
||||
|
||||
if not all_versions:
|
||||
return None
|
||||
local_versions = OpenPypeVersion.get_local_versions() if local else []
|
||||
remote_versions = OpenPypeVersion.get_remote_versions() if remote else [] # noqa: E501
|
||||
all_versions = local_versions + remote_versions + [installed_version]
|
||||
|
||||
all_versions.sort()
|
||||
return all_versions[-1]
|
||||
|
|
@ -705,7 +560,7 @@ class BootstrapRepos:
|
|||
"""Get path for specific version in list of OpenPype versions.
|
||||
|
||||
Args:
|
||||
version (str): Version string to look for (1.2.4+staging)
|
||||
version (str): Version string to look for (1.2.4-nightly.1+test)
|
||||
version_list (list of OpenPypeVersion): list of version to search.
|
||||
|
||||
Returns:
|
||||
|
|
@ -807,6 +662,8 @@ class BootstrapRepos:
|
|||
"""
|
||||
version = OpenPypeVersion.version_in_str(zip_file.name)
|
||||
destination_dir = self.data_dir / f"{version.major}.{version.minor}"
|
||||
if not destination_dir.exists():
|
||||
destination_dir.mkdir(parents=True)
|
||||
destination = destination_dir / zip_file.name
|
||||
|
||||
if destination.exists():
|
||||
|
|
@ -1131,14 +988,12 @@ class BootstrapRepos:
|
|||
|
||||
@staticmethod
|
||||
def find_openpype_version(
|
||||
version: Union[str, OpenPypeVersion],
|
||||
staging: bool
|
||||
version: Union[str, OpenPypeVersion]
|
||||
) -> Union[OpenPypeVersion, None]:
|
||||
"""Find location of specified OpenPype version.
|
||||
|
||||
Args:
|
||||
version (Union[str, OpenPypeVersion): Version to find.
|
||||
staging (bool): Filter staging versions.
|
||||
|
||||
Returns:
|
||||
requested OpenPypeVersion.
|
||||
|
|
@ -1151,9 +1006,7 @@ class BootstrapRepos:
|
|||
if installed_version == version:
|
||||
return installed_version
|
||||
|
||||
local_versions = OpenPypeVersion.get_local_versions(
|
||||
staging=staging, production=not staging
|
||||
)
|
||||
local_versions = OpenPypeVersion.get_local_versions()
|
||||
zip_version = None
|
||||
for local_version in local_versions:
|
||||
if local_version == version:
|
||||
|
|
@ -1165,37 +1018,25 @@ class BootstrapRepos:
|
|||
if zip_version is not None:
|
||||
return zip_version
|
||||
|
||||
remote_versions = OpenPypeVersion.get_remote_versions(
|
||||
staging=staging, production=not staging
|
||||
)
|
||||
for remote_version in remote_versions:
|
||||
if remote_version == version:
|
||||
return remote_version
|
||||
return None
|
||||
remote_versions = OpenPypeVersion.get_remote_versions()
|
||||
return next(
|
||||
(
|
||||
remote_version for remote_version in remote_versions
|
||||
if remote_version == version
|
||||
), None)
|
||||
|
||||
@staticmethod
|
||||
def find_latest_openpype_version(
|
||||
staging: bool
|
||||
) -> Union[OpenPypeVersion, None]:
|
||||
def find_latest_openpype_version() -> Union[OpenPypeVersion, None]:
|
||||
"""Find the latest available OpenPype version in all location.
|
||||
|
||||
Args:
|
||||
staging (bool): True to look for staging versions.
|
||||
|
||||
Returns:
|
||||
Latest OpenPype version on None if nothing was found.
|
||||
|
||||
"""
|
||||
installed_version = OpenPypeVersion.get_installed_version()
|
||||
local_versions = OpenPypeVersion.get_local_versions(
|
||||
staging=staging
|
||||
)
|
||||
remote_versions = OpenPypeVersion.get_remote_versions(
|
||||
staging=staging
|
||||
)
|
||||
all_versions = local_versions + remote_versions
|
||||
if not staging:
|
||||
all_versions.append(installed_version)
|
||||
local_versions = OpenPypeVersion.get_local_versions()
|
||||
remote_versions = OpenPypeVersion.get_remote_versions()
|
||||
all_versions = local_versions + remote_versions + [installed_version]
|
||||
|
||||
if not all_versions:
|
||||
return None
|
||||
|
|
@ -1215,7 +1056,6 @@ class BootstrapRepos:
|
|||
def find_openpype(
|
||||
self,
|
||||
openpype_path: Union[Path, str] = None,
|
||||
staging: bool = False,
|
||||
include_zips: bool = False
|
||||
) -> Union[List[OpenPypeVersion], None]:
|
||||
"""Get ordered dict of detected OpenPype version.
|
||||
|
|
@ -1229,8 +1069,6 @@ class BootstrapRepos:
|
|||
Args:
|
||||
openpype_path (Path or str, optional): Try to find OpenPype on
|
||||
the given path or url.
|
||||
staging (bool, optional): Filter only staging version, skip them
|
||||
otherwise.
|
||||
include_zips (bool, optional): If set True it will try to find
|
||||
OpenPype in zip files in given directory.
|
||||
|
||||
|
|
@ -1278,7 +1116,7 @@ class BootstrapRepos:
|
|||
for dir_to_search in dirs_to_search:
|
||||
try:
|
||||
openpype_versions += self.get_openpype_versions(
|
||||
dir_to_search, staging)
|
||||
dir_to_search)
|
||||
except ValueError:
|
||||
# location is invalid, skip it
|
||||
pass
|
||||
|
|
@ -1643,15 +1481,11 @@ class BootstrapRepos:
|
|||
return False
|
||||
return True
|
||||
|
||||
def get_openpype_versions(
|
||||
self,
|
||||
openpype_dir: Path,
|
||||
staging: bool = False) -> list:
|
||||
def get_openpype_versions(self, openpype_dir: Path) -> list:
|
||||
"""Get all detected OpenPype versions in directory.
|
||||
|
||||
Args:
|
||||
openpype_dir (Path): Directory to scan.
|
||||
staging (bool, optional): Find staging versions if True.
|
||||
|
||||
Returns:
|
||||
list of OpenPypeVersion
|
||||
|
|
@ -1669,8 +1503,7 @@ class BootstrapRepos:
|
|||
for item in openpype_dir.iterdir():
|
||||
# if the item is directory with major.minor version, dive deeper
|
||||
if item.is_dir() and re.match(r"^\d+\.\d+$", item.name):
|
||||
_versions = self.get_openpype_versions(
|
||||
item, staging=staging)
|
||||
_versions = self.get_openpype_versions(item)
|
||||
if _versions:
|
||||
openpype_versions += _versions
|
||||
|
||||
|
|
@ -1693,11 +1526,7 @@ class BootstrapRepos:
|
|||
continue
|
||||
|
||||
detected_version.path = item
|
||||
if staging and detected_version.is_staging():
|
||||
openpype_versions.append(detected_version)
|
||||
|
||||
if not staging and not detected_version.is_staging():
|
||||
openpype_versions.append(detected_version)
|
||||
openpype_versions.append(detected_version)
|
||||
|
||||
return sorted(openpype_versions)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ import sys
|
|||
import re
|
||||
import collections
|
||||
|
||||
from Qt import QtCore, QtGui, QtWidgets # noqa
|
||||
from Qt.QtGui import QValidator # noqa
|
||||
from Qt.QtCore import QTimer # noqa
|
||||
from qtpy import QtCore, QtGui, QtWidgets
|
||||
|
||||
from .install_thread import InstallThread
|
||||
from .tools import (
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import os
|
|||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from Qt.QtCore import QThread, Signal, QObject # noqa
|
||||
from qtpy import QtCore
|
||||
|
||||
from .bootstrap_repos import (
|
||||
BootstrapRepos,
|
||||
|
|
@ -17,7 +17,7 @@ from .bootstrap_repos import (
|
|||
from .tools import validate_mongo_connection
|
||||
|
||||
|
||||
class InstallThread(QThread):
|
||||
class InstallThread(QtCore.QThread):
|
||||
"""Install Worker thread.
|
||||
|
||||
This class takes care of finding OpenPype version on user entered path
|
||||
|
|
@ -28,15 +28,14 @@ class InstallThread(QThread):
|
|||
user data dir.
|
||||
|
||||
"""
|
||||
progress = Signal(int)
|
||||
message = Signal((str, bool))
|
||||
progress = QtCore.Signal(int)
|
||||
message = QtCore.Signal((str, bool))
|
||||
|
||||
def __init__(self, parent=None,):
|
||||
self._mongo = None
|
||||
self._path = None
|
||||
self._result = None
|
||||
|
||||
QThread.__init__(self, parent)
|
||||
super().__init__(parent)
|
||||
|
||||
def result(self):
|
||||
"""Result of finished installation."""
|
||||
|
|
@ -62,143 +61,117 @@ class InstallThread(QThread):
|
|||
progress_callback=self.set_progress, message=self.message)
|
||||
local_version = OpenPypeVersion.get_installed_version_str()
|
||||
|
||||
# if user did enter nothing, we install OpenPype from local version.
|
||||
# zip content of `repos`, copy it to user data dir and append
|
||||
# version to it.
|
||||
if not self._path:
|
||||
# user did not entered url
|
||||
if not self._mongo:
|
||||
# it not set in environment
|
||||
if not os.getenv("OPENPYPE_MONGO"):
|
||||
# try to get it from settings registry
|
||||
try:
|
||||
self._mongo = bs.secure_registry.get_item(
|
||||
"openPypeMongo")
|
||||
except ValueError:
|
||||
self.message.emit(
|
||||
"!!! We need MongoDB URL to proceed.", True)
|
||||
self._set_result(-1)
|
||||
return
|
||||
else:
|
||||
self._mongo = os.getenv("OPENPYPE_MONGO")
|
||||
else:
|
||||
self.message.emit("Saving mongo connection string ...", False)
|
||||
bs.secure_registry.set_item("openPypeMongo", self._mongo)
|
||||
|
||||
os.environ["OPENPYPE_MONGO"] = self._mongo
|
||||
|
||||
self.message.emit(
|
||||
f"Detecting installed OpenPype versions in {bs.data_dir}",
|
||||
False)
|
||||
detected = bs.find_openpype(include_zips=True)
|
||||
|
||||
if detected:
|
||||
if not OpenPypeVersion.get_installed_version().is_compatible(
|
||||
detected[-1]):
|
||||
self.message.emit((
|
||||
f"Latest detected version {detected[-1]} "
|
||||
"is not compatible with the currently running "
|
||||
f"{local_version}"
|
||||
), True)
|
||||
self.message.emit((
|
||||
"Filtering detected versions to compatible ones..."
|
||||
), False)
|
||||
|
||||
detected = [
|
||||
version for version in detected
|
||||
if version.is_compatible(
|
||||
OpenPypeVersion.get_installed_version())
|
||||
]
|
||||
|
||||
if OpenPypeVersion(
|
||||
version=local_version, path=Path()) < detected[-1]:
|
||||
self.message.emit((
|
||||
f"Latest installed version {detected[-1]} is newer "
|
||||
f"then currently running {local_version}"
|
||||
), False)
|
||||
self.message.emit("Skipping OpenPype install ...", False)
|
||||
if detected[-1].path.suffix.lower() == ".zip":
|
||||
bs.extract_openpype(detected[-1])
|
||||
self._set_result(0)
|
||||
return
|
||||
|
||||
if OpenPypeVersion(version=local_version).get_main_version() == detected[-1].get_main_version(): # noqa
|
||||
self.message.emit((
|
||||
f"Latest installed version is the same as "
|
||||
f"currently running {local_version}"
|
||||
), False)
|
||||
self.message.emit("Skipping OpenPype install ...", False)
|
||||
self._set_result(0)
|
||||
return
|
||||
|
||||
self.message.emit((
|
||||
"All installed versions are older then "
|
||||
f"currently running one {local_version}"
|
||||
), False)
|
||||
else:
|
||||
if getattr(sys, 'frozen', False):
|
||||
self.message.emit("None detected.", True)
|
||||
self.message.emit(("We will use OpenPype coming with "
|
||||
"installer."), False)
|
||||
openpype_version = bs.create_version_from_frozen_code()
|
||||
if not openpype_version:
|
||||
self.message.emit(
|
||||
f"!!! Install failed - {openpype_version}", True)
|
||||
self._set_result(-1)
|
||||
return
|
||||
self.message.emit(f"Using: {openpype_version}", False)
|
||||
bs.install_version(openpype_version)
|
||||
self.message.emit(f"Installed as {openpype_version}", False)
|
||||
self.progress.emit(100)
|
||||
self._set_result(1)
|
||||
return
|
||||
else:
|
||||
self.message.emit("None detected.", False)
|
||||
|
||||
self.message.emit(
|
||||
f"We will use local OpenPype version {local_version}", False)
|
||||
|
||||
local_openpype = bs.create_version_from_live_code()
|
||||
if not local_openpype:
|
||||
self.message.emit(
|
||||
f"!!! Install failed - {local_openpype}", True)
|
||||
self._set_result(-1)
|
||||
return
|
||||
# user did not entered url
|
||||
if self._mongo:
|
||||
self.message.emit("Saving mongo connection string ...", False)
|
||||
bs.secure_registry.set_item("openPypeMongo", self._mongo)
|
||||
|
||||
elif os.getenv("OPENPYPE_MONGO"):
|
||||
self._mongo = os.getenv("OPENPYPE_MONGO")
|
||||
else:
|
||||
# try to get it from settings registry
|
||||
try:
|
||||
bs.install_version(local_openpype)
|
||||
except (OpenPypeVersionExists,
|
||||
OpenPypeVersionInvalid,
|
||||
OpenPypeVersionIOError) as e:
|
||||
self.message.emit(f"Installed failed: ", True)
|
||||
self.message.emit(str(e), True)
|
||||
self._mongo = bs.secure_registry.get_item(
|
||||
"openPypeMongo")
|
||||
except ValueError:
|
||||
self.message.emit(
|
||||
"!!! We need MongoDB URL to proceed.", True)
|
||||
self._set_result(-1)
|
||||
return
|
||||
os.environ["OPENPYPE_MONGO"] = self._mongo
|
||||
|
||||
self.message.emit(f"Installed as {local_openpype}", False)
|
||||
self.message.emit(
|
||||
f"Detecting installed OpenPype versions in {bs.data_dir}",
|
||||
False)
|
||||
detected = bs.find_openpype(include_zips=True)
|
||||
if not detected and getattr(sys, 'frozen', False):
|
||||
self.message.emit("None detected.", True)
|
||||
self.message.emit(("We will use OpenPype coming with "
|
||||
"installer."), False)
|
||||
openpype_version = bs.create_version_from_frozen_code()
|
||||
if not openpype_version:
|
||||
self.message.emit(
|
||||
f"!!! Install failed - {openpype_version}", True)
|
||||
self._set_result(-1)
|
||||
return
|
||||
self.message.emit(f"Using: {openpype_version}", False)
|
||||
bs.install_version(openpype_version)
|
||||
self.message.emit(f"Installed as {openpype_version}", False)
|
||||
self.progress.emit(100)
|
||||
self._set_result(1)
|
||||
return
|
||||
else:
|
||||
# if we have mongo connection string, validate it, set it to
|
||||
# user settings and get OPENPYPE_PATH from there.
|
||||
if self._mongo:
|
||||
if not validate_mongo_connection(self._mongo):
|
||||
self.message.emit(
|
||||
f"!!! invalid mongo url {self._mongo}", True)
|
||||
self._set_result(-1)
|
||||
return
|
||||
bs.secure_registry.set_item("openPypeMongo", self._mongo)
|
||||
os.environ["OPENPYPE_MONGO"] = self._mongo
|
||||
|
||||
self.message.emit(f"processing {self._path}", True)
|
||||
repo_file = bs.process_entered_location(self._path)
|
||||
if detected and not OpenPypeVersion.get_installed_version().is_compatible(detected[-1]): # noqa: E501
|
||||
self.message.emit((
|
||||
f"Latest detected version {detected[-1]} "
|
||||
"is not compatible with the currently running "
|
||||
f"{local_version}"
|
||||
), True)
|
||||
self.message.emit((
|
||||
"Filtering detected versions to compatible ones..."
|
||||
), False)
|
||||
|
||||
if not repo_file:
|
||||
self.message.emit("!!! Cannot install", True)
|
||||
self._set_result(-1)
|
||||
# filter results to get only compatible versions
|
||||
detected = [
|
||||
version for version in detected
|
||||
if version.is_compatible(
|
||||
OpenPypeVersion.get_installed_version())
|
||||
]
|
||||
|
||||
if detected:
|
||||
if OpenPypeVersion(
|
||||
version=local_version, path=Path()) < detected[-1]:
|
||||
self.message.emit((
|
||||
f"Latest installed version {detected[-1]} is newer "
|
||||
f"then currently running {local_version}"
|
||||
), False)
|
||||
self.message.emit("Skipping OpenPype install ...", False)
|
||||
if detected[-1].path.suffix.lower() == ".zip":
|
||||
bs.extract_openpype(detected[-1])
|
||||
self._set_result(0)
|
||||
return
|
||||
|
||||
if OpenPypeVersion(version=local_version).get_main_version() == detected[-1].get_main_version(): # noqa: E501
|
||||
self.message.emit((
|
||||
f"Latest installed version is the same as "
|
||||
f"currently running {local_version}"
|
||||
), False)
|
||||
self.message.emit("Skipping OpenPype install ...", False)
|
||||
self._set_result(0)
|
||||
return
|
||||
|
||||
self.message.emit((
|
||||
"All installed versions are older then "
|
||||
f"currently running one {local_version}"
|
||||
), False)
|
||||
|
||||
self.message.emit("None detected.", False)
|
||||
|
||||
self.message.emit(
|
||||
f"We will use local OpenPype version {local_version}", False)
|
||||
|
||||
local_openpype = bs.create_version_from_live_code()
|
||||
if not local_openpype:
|
||||
self.message.emit(
|
||||
f"!!! Install failed - {local_openpype}", True)
|
||||
self._set_result(-1)
|
||||
return
|
||||
|
||||
try:
|
||||
bs.install_version(local_openpype)
|
||||
except (OpenPypeVersionExists,
|
||||
OpenPypeVersionInvalid,
|
||||
OpenPypeVersionIOError) as e:
|
||||
self.message.emit(f"Installed failed: ", True)
|
||||
self.message.emit(str(e), True)
|
||||
self._set_result(-1)
|
||||
return
|
||||
|
||||
self.message.emit(f"Installed as {local_openpype}", False)
|
||||
self.progress.emit(100)
|
||||
self._set_result(1)
|
||||
return
|
||||
|
||||
self.progress.emit(100)
|
||||
self._set_result(1)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from Qt import QtWidgets, QtGui
|
||||
from qtpy import QtWidgets, QtGui
|
||||
|
||||
from .tools import (
|
||||
load_stylesheet,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from Qt import QtCore, QtGui, QtWidgets # noqa
|
||||
from qtpy import QtWidgets
|
||||
|
||||
|
||||
class NiceProgressBar(QtWidgets.QProgressBar):
|
||||
|
|
|
|||
|
|
@ -153,7 +153,8 @@ def get_openpype_global_settings(url: str) -> dict:
|
|||
# Create mongo connection
|
||||
client = MongoClient(url, **kwargs)
|
||||
# Access settings collection
|
||||
col = client["openpype"]["settings"]
|
||||
openpype_db = os.environ.get("OPENPYPE_DATABASE_NAME") or "openpype"
|
||||
col = client[openpype_db]["settings"]
|
||||
# Query global settings
|
||||
global_settings = col.find_one({"type": "global_settings"}) or {}
|
||||
# Close Mongo connection
|
||||
|
|
@ -184,11 +185,7 @@ def get_openpype_path_from_settings(settings: dict) -> Union[str, None]:
|
|||
if paths and isinstance(paths, str):
|
||||
paths = [paths]
|
||||
|
||||
# Loop over paths and return only existing
|
||||
for path in paths:
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
return None
|
||||
return next((path for path in paths if os.path.exists(path)), None)
|
||||
|
||||
|
||||
def get_expected_studio_version_str(
|
||||
|
|
@ -206,10 +203,7 @@ def get_expected_studio_version_str(
|
|||
mongo_url = os.environ.get("OPENPYPE_MONGO")
|
||||
if global_settings is None:
|
||||
global_settings = get_openpype_global_settings(mongo_url)
|
||||
if staging:
|
||||
key = "staging_version"
|
||||
else:
|
||||
key = "production_version"
|
||||
key = "staging_version" if staging else "production_version"
|
||||
return global_settings.get(key) or ""
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Working thread for update."""
|
||||
from Qt.QtCore import QThread, Signal, QObject # noqa
|
||||
from qtpy import QtCore
|
||||
|
||||
from .bootstrap_repos import (
|
||||
BootstrapRepos,
|
||||
|
|
@ -8,7 +8,7 @@ from .bootstrap_repos import (
|
|||
)
|
||||
|
||||
|
||||
class UpdateThread(QThread):
|
||||
class UpdateThread(QtCore.QThread):
|
||||
"""Install Worker thread.
|
||||
|
||||
This class takes care of finding OpenPype version on user entered path
|
||||
|
|
@ -19,13 +19,13 @@ class UpdateThread(QThread):
|
|||
user data dir.
|
||||
|
||||
"""
|
||||
progress = Signal(int)
|
||||
message = Signal((str, bool))
|
||||
progress = QtCore.Signal(int)
|
||||
message = QtCore.Signal((str, bool))
|
||||
|
||||
def __init__(self, parent=None):
|
||||
self._result = None
|
||||
self._openpype_version = None
|
||||
QThread.__init__(self, parent)
|
||||
super().__init__(parent)
|
||||
|
||||
def set_version(self, openpype_version: OpenPypeVersion):
|
||||
self._openpype_version = openpype_version
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Progress window to show when OpenPype is updating/installing locally."""
|
||||
import os
|
||||
|
||||
from qtpy import QtCore, QtGui, QtWidgets
|
||||
|
||||
from .update_thread import UpdateThread
|
||||
from Qt import QtCore, QtGui, QtWidgets # noqa
|
||||
from .bootstrap_repos import OpenPypeVersion
|
||||
from .nice_progress_bar import NiceProgressBar
|
||||
from .tools import load_stylesheet
|
||||
|
|
@ -47,7 +49,6 @@ class UpdateWindow(QtWidgets.QDialog):
|
|||
|
||||
self._update_thread = None
|
||||
|
||||
self.resize(QtCore.QSize(self._width, self._height))
|
||||
self._init_ui()
|
||||
|
||||
# Set stylesheet
|
||||
|
|
@ -79,6 +80,16 @@ class UpdateWindow(QtWidgets.QDialog):
|
|||
|
||||
self._progress_bar = progress_bar
|
||||
|
||||
def showEvent(self, event):
|
||||
super().showEvent(event)
|
||||
current_size = self.size()
|
||||
new_size = QtCore.QSize(
|
||||
max(current_size.width(), self._width),
|
||||
max(current_size.height(), self._height)
|
||||
)
|
||||
if current_size != new_size:
|
||||
self.resize(new_size)
|
||||
|
||||
def _run_update(self):
|
||||
"""Start install process.
|
||||
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@ Source: "build\{#build}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdir
|
|||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
[Icons]
|
||||
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\openpype_gui.exe"
|
||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\openpype_gui.exe"; Tasks: desktopicon
|
||||
Name: "{autoprograms}\{#MyAppName} {#AppVer}"; Filename: "{app}\openpype_gui.exe"
|
||||
Name: "{autodesktop}\{#MyAppName} {#AppVer}"; Filename: "{app}\openpype_gui.exe"; Tasks: desktopicon
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\openpype_gui.exe"; Description: "{cm:LaunchProgram,OpenPype}"; Flags: nowait postinstall skipifsilent
|
||||
|
|
|
|||
112
openpype/api.py
112
openpype/api.py
|
|
@ -1,112 +0,0 @@
|
|||
from .settings import (
|
||||
get_system_settings,
|
||||
get_project_settings,
|
||||
get_current_project_settings,
|
||||
get_anatomy_settings,
|
||||
|
||||
SystemSettings,
|
||||
ProjectSettings
|
||||
)
|
||||
from .lib import (
|
||||
PypeLogger,
|
||||
Logger,
|
||||
Anatomy,
|
||||
execute,
|
||||
run_subprocess,
|
||||
version_up,
|
||||
get_asset,
|
||||
get_workdir_data,
|
||||
get_version_from_path,
|
||||
get_last_version_from_path,
|
||||
get_app_environments_for_context,
|
||||
source_hash,
|
||||
get_latest_version,
|
||||
get_local_site_id,
|
||||
change_openpype_mongo_url,
|
||||
create_project_folders,
|
||||
get_project_basic_paths
|
||||
)
|
||||
|
||||
from .lib.mongo import (
|
||||
get_default_components
|
||||
)
|
||||
|
||||
from .lib.applications import (
|
||||
ApplicationManager
|
||||
)
|
||||
|
||||
from .lib.avalon_context import (
|
||||
BuildWorkfile
|
||||
)
|
||||
|
||||
from . import resources
|
||||
|
||||
from .plugin import (
|
||||
Extractor,
|
||||
|
||||
ValidatePipelineOrder,
|
||||
ValidateContentsOrder,
|
||||
ValidateSceneOrder,
|
||||
ValidateMeshOrder,
|
||||
)
|
||||
|
||||
# temporary fix, might
|
||||
from .action import (
|
||||
get_errored_instances_from_context,
|
||||
RepairAction,
|
||||
RepairContextAction
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"get_system_settings",
|
||||
"get_project_settings",
|
||||
"get_current_project_settings",
|
||||
"get_anatomy_settings",
|
||||
"get_project_basic_paths",
|
||||
|
||||
"SystemSettings",
|
||||
"ProjectSettings",
|
||||
|
||||
"PypeLogger",
|
||||
"Logger",
|
||||
"Anatomy",
|
||||
"execute",
|
||||
"get_default_components",
|
||||
"ApplicationManager",
|
||||
"BuildWorkfile",
|
||||
|
||||
# Resources
|
||||
"resources",
|
||||
|
||||
# plugin classes
|
||||
"Extractor",
|
||||
# ordering
|
||||
"ValidatePipelineOrder",
|
||||
"ValidateContentsOrder",
|
||||
"ValidateSceneOrder",
|
||||
"ValidateMeshOrder",
|
||||
# action
|
||||
"get_errored_instances_from_context",
|
||||
"RepairAction",
|
||||
"RepairContextAction",
|
||||
|
||||
# get contextual data
|
||||
"version_up",
|
||||
"get_asset",
|
||||
"get_workdir_data",
|
||||
"get_version_from_path",
|
||||
"get_last_version_from_path",
|
||||
"get_app_environments_for_context",
|
||||
"source_hash",
|
||||
|
||||
"run_subprocess",
|
||||
"get_latest_version",
|
||||
|
||||
"get_local_site_id",
|
||||
"change_openpype_mongo_url",
|
||||
|
||||
"get_project_basic_paths",
|
||||
"create_project_folders"
|
||||
|
||||
]
|
||||
|
|
@ -16,14 +16,15 @@ from .pype_commands import PypeCommands
|
|||
@click.option("--use-staging", is_flag=True,
|
||||
expose_value=False, help="use staging variants")
|
||||
@click.option("--list-versions", is_flag=True, expose_value=False,
|
||||
help=("list all detected versions. Use With `--use-staging "
|
||||
"to list staging versions."))
|
||||
help="list all detected versions.")
|
||||
@click.option("--validate-version", expose_value=False,
|
||||
help="validate given version integrity")
|
||||
@click.option("--debug", is_flag=True, expose_value=False,
|
||||
help=("Enable debug"))
|
||||
help="Enable debug")
|
||||
@click.option("--verbose", expose_value=False,
|
||||
help=("Change OpenPype log level (debug - critical or 0-50)"))
|
||||
@click.option("--automatic-tests", is_flag=True, expose_value=False,
|
||||
help=("Run in automatic tests mode"))
|
||||
def main(ctx):
|
||||
"""Pype is main command serving as entry point to pipeline system.
|
||||
|
||||
|
|
@ -429,20 +430,18 @@ def unpack_project(zipfile, root):
|
|||
|
||||
@main.command()
|
||||
def interactive():
|
||||
"""Interative (Python like) console.
|
||||
"""Interactive (Python like) console.
|
||||
|
||||
Helpfull command not only for development to directly work with python
|
||||
Helpful command not only for development to directly work with python
|
||||
interpreter.
|
||||
|
||||
Warning:
|
||||
Executable 'openpype_gui' on windows won't work.
|
||||
Executable 'openpype_gui' on Windows won't work.
|
||||
"""
|
||||
|
||||
from openpype.version import __version__
|
||||
|
||||
banner = "OpenPype {}\nPython {} on {}".format(
|
||||
__version__, sys.version, sys.platform
|
||||
)
|
||||
banner = f"OpenPype {__version__}\nPython {sys.version} on {sys.platform}"
|
||||
code.interact(banner)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -164,7 +164,6 @@ def get_linked_representation_id(
|
|||
# Recursive graph lookup for inputs
|
||||
{"$graphLookup": graph_lookup}
|
||||
]
|
||||
|
||||
conn = get_project_connection(project_name)
|
||||
result = conn.aggregate(query_pipeline)
|
||||
referenced_version_ids = _process_referenced_pipeline_result(
|
||||
|
|
@ -213,7 +212,7 @@ def _process_referenced_pipeline_result(result, link_type):
|
|||
|
||||
for output in sorted(outputs_recursive, key=lambda o: o["depth"]):
|
||||
output_links = output.get("data", {}).get("inputLinks")
|
||||
if not output_links:
|
||||
if not output_links and output["type"] != "hero_version":
|
||||
continue
|
||||
|
||||
# Leaf
|
||||
|
|
@ -232,6 +231,9 @@ def _process_referenced_pipeline_result(result, link_type):
|
|||
|
||||
|
||||
def _filter_input_links(input_links, link_type, correctly_linked_ids):
|
||||
if not input_links: # to handle hero versions
|
||||
return
|
||||
|
||||
for input_link in input_links:
|
||||
if link_type and input_link["type"] != link_type:
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
|
||||
from openpype.lib import PreLaunchHook
|
||||
|
||||
|
||||
|
|
@ -40,5 +41,13 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook):
|
|||
self.log.info("Current context does not have any workfile yet.")
|
||||
return
|
||||
|
||||
# Determine whether to open workfile post initialization.
|
||||
if self.host_name == "maya":
|
||||
key = "open_workfile_post_initialization"
|
||||
if self.data["project_settings"]["maya"][key]:
|
||||
self.log.debug("Opening workfile post initialization.")
|
||||
self.data["env"]["OPENPYPE_" + key.upper()] = "1"
|
||||
return
|
||||
|
||||
# Add path to workfile to arguments
|
||||
self.launch_context.launch_args.append(last_workfile)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class LaunchFoundryAppsWindows(PreLaunchHook):
|
|||
|
||||
# Should be as last hook because must change launch arguments to string
|
||||
order = 1000
|
||||
app_groups = ["nuke", "nukex", "hiero", "nukestudio"]
|
||||
app_groups = ["nuke", "nukeassist", "nukex", "hiero", "nukestudio"]
|
||||
platforms = ["windows"]
|
||||
|
||||
def execute(self):
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ exists is used.
|
|||
|
||||
import os
|
||||
from abc import ABCMeta, abstractmethod
|
||||
import platform
|
||||
|
||||
import six
|
||||
|
||||
|
|
@ -187,11 +188,19 @@ class HostDirmap(object):
|
|||
|
||||
self.log.debug("local overrides {}".format(active_overrides))
|
||||
self.log.debug("remote overrides {}".format(remote_overrides))
|
||||
current_platform = platform.system().lower()
|
||||
for root_name, active_site_dir in active_overrides.items():
|
||||
remote_site_dir = (
|
||||
remote_overrides.get(root_name)
|
||||
or sync_settings["sites"][remote_site]["root"][root_name]
|
||||
)
|
||||
|
||||
if isinstance(remote_site_dir, dict):
|
||||
remote_site_dir = remote_site_dir.get(current_platform)
|
||||
|
||||
if not remote_site_dir:
|
||||
continue
|
||||
|
||||
if os.path.isdir(active_site_dir):
|
||||
if "destination-path" not in mapping:
|
||||
mapping["destination-path"] = []
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import os
|
||||
import logging
|
||||
import contextlib
|
||||
from abc import ABCMeta, abstractproperty
|
||||
|
|
@ -76,6 +77,18 @@ class HostBase(object):
|
|||
|
||||
pass
|
||||
|
||||
def install(self):
|
||||
"""Install host specific functionality.
|
||||
|
||||
This is where should be added menu with tools, registered callbacks
|
||||
and other host integration initialization.
|
||||
|
||||
It is called automatically when 'openpype.pipeline.install_host' is
|
||||
triggered.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@property
|
||||
def log(self):
|
||||
if self._log is None:
|
||||
|
|
@ -88,6 +101,30 @@ class HostBase(object):
|
|||
|
||||
pass
|
||||
|
||||
def get_current_project_name(self):
|
||||
"""
|
||||
Returns:
|
||||
Union[str, None]: Current project name.
|
||||
"""
|
||||
|
||||
return os.environ.get("AVALON_PROJECT")
|
||||
|
||||
def get_current_asset_name(self):
|
||||
"""
|
||||
Returns:
|
||||
Union[str, None]: Current asset name.
|
||||
"""
|
||||
|
||||
return os.environ.get("AVALON_ASSET")
|
||||
|
||||
def get_current_task_name(self):
|
||||
"""
|
||||
Returns:
|
||||
Union[str, None]: Current task name.
|
||||
"""
|
||||
|
||||
return os.environ.get("AVALON_TASK")
|
||||
|
||||
def get_current_context(self):
|
||||
"""Get current context information.
|
||||
|
||||
|
|
@ -99,19 +136,14 @@ class HostBase(object):
|
|||
Default implementation returns values from 'legacy_io.Session'.
|
||||
|
||||
Returns:
|
||||
dict: Context with 3 keys 'project_name', 'asset_name' and
|
||||
'task_name'. All of them can be 'None'.
|
||||
Dict[str, Union[str, None]]: Context with 3 keys 'project_name',
|
||||
'asset_name' and 'task_name'. All of them can be 'None'.
|
||||
"""
|
||||
|
||||
from openpype.pipeline import legacy_io
|
||||
|
||||
if legacy_io.is_installed():
|
||||
legacy_io.install()
|
||||
|
||||
return {
|
||||
"project_name": legacy_io.Session["AVALON_PROJECT"],
|
||||
"asset_name": legacy_io.Session["AVALON_ASSET"],
|
||||
"task_name": legacy_io.Session["AVALON_TASK"]
|
||||
"project_name": self.get_current_project_name(),
|
||||
"asset_name": self.get_current_asset_name(),
|
||||
"task_name": self.get_current_task_name()
|
||||
}
|
||||
|
||||
def get_context_title(self):
|
||||
|
|
|
|||
|
|
@ -10,30 +10,15 @@ from .launch_logic import (
|
|||
)
|
||||
|
||||
from .pipeline import (
|
||||
AfterEffectsHost,
|
||||
ls,
|
||||
get_asset_settings,
|
||||
install,
|
||||
uninstall,
|
||||
list_instances,
|
||||
remove_instance,
|
||||
containerise,
|
||||
get_context_data,
|
||||
update_context_data,
|
||||
get_context_title
|
||||
)
|
||||
|
||||
from .workio import (
|
||||
file_extensions,
|
||||
has_unsaved_changes,
|
||||
save_file,
|
||||
open_file,
|
||||
current_file,
|
||||
work_root,
|
||||
containerise
|
||||
)
|
||||
|
||||
from .lib import (
|
||||
maintained_selection,
|
||||
get_extension_manifest_path
|
||||
get_extension_manifest_path,
|
||||
get_asset_settings
|
||||
)
|
||||
|
||||
from .plugin import (
|
||||
|
|
@ -48,26 +33,12 @@ __all__ = [
|
|||
|
||||
# pipeline
|
||||
"ls",
|
||||
"get_asset_settings",
|
||||
"install",
|
||||
"uninstall",
|
||||
"list_instances",
|
||||
"remove_instance",
|
||||
"containerise",
|
||||
"get_context_data",
|
||||
"update_context_data",
|
||||
"get_context_title",
|
||||
|
||||
"file_extensions",
|
||||
"has_unsaved_changes",
|
||||
"save_file",
|
||||
"open_file",
|
||||
"current_file",
|
||||
"work_root",
|
||||
|
||||
# lib
|
||||
"maintained_selection",
|
||||
"get_extension_manifest_path",
|
||||
"get_asset_settings",
|
||||
|
||||
# plugin
|
||||
"AfterEffectsLoader"
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ExtensionManifest Version="8.0" ExtensionBundleId="com.openpype.AE.panel" ExtensionBundleVersion="1.0.23"
|
||||
<ExtensionManifest Version="8.0" ExtensionBundleId="com.openpype.AE.panel" ExtensionBundleVersion="1.0.24"
|
||||
ExtensionBundleName="openpype" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<ExtensionList>
|
||||
<Extension Id="com.openpype.AE.panel" Version="1.0" />
|
||||
|
|
|
|||
|
|
@ -38,17 +38,6 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#creator-button").bind("click", function() {
|
||||
RPC.call('AfterEffects.creator_route').then(function (data) {
|
||||
}, function (error) {
|
||||
alert(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#loader-button").bind("click", function() {
|
||||
|
|
@ -82,17 +71,6 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#subsetmanager-button").bind("click", function() {
|
||||
RPC.call('AfterEffects.subsetmanager_route').then(function (data) {
|
||||
}, function (error) {
|
||||
alert(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#experimental-button").bind("click", function() {
|
||||
|
|
@ -113,11 +91,9 @@
|
|||
|
||||
<div>
|
||||
<div></div><a href=# id=workfiles-button><button class="hostFontSize">Workfiles...</button></a></div>
|
||||
<div> <a href=# id=creator-button><button class="hostFontSize">Create...</button></a></div>
|
||||
<div><a href=# id=loader-button><button class="hostFontSize">Load...</button></a></div>
|
||||
<div><a href=# id=publish-button><button class="hostFontSize">Publish...</button></a></div>
|
||||
<div><a href=# id=sceneinventory-button><button class="hostFontSize">Manage...</button></a></div>
|
||||
<div><a href=# id=subsetmanager-button><button class="hostFontSize">Subset Manager...</button></a></div>
|
||||
<div><a href=# id=experimental-button><button class="hostFontSize">Experimental Tools...</button></a></div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ function main(websocket_url){
|
|||
|
||||
RPC.addRoute('AfterEffects.get_render_info', function (data) {
|
||||
log.warn('Server called client route "get_render_info":', data);
|
||||
return runEvalScript("getRenderInfo()")
|
||||
return runEvalScript("getRenderInfo(" + data.comp_id +")")
|
||||
.then(function(result){
|
||||
log.warn("get_render_info: " + result);
|
||||
return result;
|
||||
|
|
@ -289,7 +289,7 @@ function main(websocket_url){
|
|||
RPC.addRoute('AfterEffects.render', function (data) {
|
||||
log.warn('Server called client route "render":', data);
|
||||
var escapedPath = EscapeStringForJSX(data.folder_url);
|
||||
return runEvalScript("render('" + escapedPath +"')")
|
||||
return runEvalScript("render('" + escapedPath +"', " + data.comp_id + ")")
|
||||
.then(function(result){
|
||||
log.warn("render: " + result);
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -395,41 +395,84 @@ function saveAs(path){
|
|||
app.project.save(fp = new File(path));
|
||||
}
|
||||
|
||||
function getRenderInfo(){
|
||||
function getRenderInfo(comp_id){
|
||||
/***
|
||||
Get info from render queue.
|
||||
Currently pulls only file name to parse extension and
|
||||
Currently pulls only file name to parse extension and
|
||||
if it is sequence in Python
|
||||
Args:
|
||||
comp_id (int): id of composition
|
||||
Return:
|
||||
(list) [{file_name:"xx.png", width:00, height:00}]
|
||||
**/
|
||||
var item = app.project.itemByID(comp_id);
|
||||
if (!item){
|
||||
return _prepareError("Composition with '" + comp_id + "' wasn't found! Recreate publishable instance(s)")
|
||||
}
|
||||
|
||||
var comp_name = item.name;
|
||||
var output_metadata = []
|
||||
try{
|
||||
var render_item = app.project.renderQueue.item(1);
|
||||
if (render_item.status == RQItemStatus.DONE){
|
||||
render_item.duplicate(); // create new, cannot change status if DONE
|
||||
render_item.remove(); // remove existing to limit duplications
|
||||
render_item = app.project.renderQueue.item(1);
|
||||
// render_item.duplicate() should create new item on renderQueue
|
||||
// BUT it works only sometimes, there are some weird synchronization issue
|
||||
// this method will be called always before render, so prepare items here
|
||||
// for render to spare the hassle
|
||||
for (i = 1; i <= app.project.renderQueue.numItems; ++i){
|
||||
var render_item = app.project.renderQueue.item(i);
|
||||
if (render_item.comp.id != comp_id){
|
||||
continue;
|
||||
}
|
||||
|
||||
if (render_item.status == RQItemStatus.DONE){
|
||||
render_item.duplicate(); // create new, cannot change status if DONE
|
||||
render_item.remove(); // remove existing to limit duplications
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
render_item.render = true; // always set render queue to render
|
||||
var item = render_item.outputModule(1);
|
||||
// properly validate as `numItems` won't change magically
|
||||
var comp_id_count = 0;
|
||||
for (i = 1; i <= app.project.renderQueue.numItems; ++i){
|
||||
var render_item = app.project.renderQueue.item(i);
|
||||
if (render_item.comp.id != comp_id){
|
||||
continue;
|
||||
}
|
||||
comp_id_count += 1;
|
||||
var item = render_item.outputModule(1);
|
||||
|
||||
for (j = 1; j<= render_item.numOutputModules; ++j){
|
||||
var file_url = item.file.toString();
|
||||
output_metadata.push(
|
||||
JSON.stringify({
|
||||
"file_name": file_url,
|
||||
"width": render_item.comp.width,
|
||||
"height": render_item.comp.height
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return _prepareError("There is no render queue, create one");
|
||||
}
|
||||
var file_url = item.file.toString();
|
||||
|
||||
return JSON.stringify({
|
||||
"file_name": file_url,
|
||||
"width": render_item.comp.width,
|
||||
"height": render_item.comp.height
|
||||
})
|
||||
if (comp_id_count > 1){
|
||||
return _prepareError("There cannot be more items in Render Queue for '" + comp_name + "'!")
|
||||
}
|
||||
|
||||
if (comp_id_count == 0){
|
||||
return _prepareError("There is no item in Render Queue for '" + comp_name + "'! Add composition to Render Queue.")
|
||||
}
|
||||
|
||||
return '[' + output_metadata.join() + ']';
|
||||
}
|
||||
|
||||
function getAudioUrlForComp(comp_id){
|
||||
/**
|
||||
* Searches composition for audio layer
|
||||
*
|
||||
*
|
||||
* Only single AVLayer is expected!
|
||||
* Used for collecting Audio
|
||||
*
|
||||
*
|
||||
* Args:
|
||||
* comp_id (int): id of composition
|
||||
* Return:
|
||||
|
|
@ -457,7 +500,7 @@ function addItemAsLayerToComp(comp_id, item_id, found_comp){
|
|||
/**
|
||||
* Adds already imported FootageItem ('item_id') as a new
|
||||
* layer to composition ('comp_id').
|
||||
*
|
||||
*
|
||||
* Args:
|
||||
* comp_id (int): id of target composition
|
||||
* item_id (int): FootageItem.id
|
||||
|
|
@ -480,17 +523,17 @@ function addItemAsLayerToComp(comp_id, item_id, found_comp){
|
|||
function importBackground(comp_id, composition_name, files_to_import){
|
||||
/**
|
||||
* Imports backgrounds images to existing or new composition.
|
||||
*
|
||||
*
|
||||
* If comp_id is not provided, new composition is created, basic
|
||||
* values (width, heights, frameRatio) takes from first imported
|
||||
* image.
|
||||
*
|
||||
*
|
||||
* Args:
|
||||
* comp_id (int): id of existing composition (null if new)
|
||||
* composition_name (str): used when new composition
|
||||
* composition_name (str): used when new composition
|
||||
* files_to_import (list): list of absolute paths to import and
|
||||
* add as layers
|
||||
*
|
||||
*
|
||||
* Returns:
|
||||
* (str): json representation (id, name, members)
|
||||
*/
|
||||
|
|
@ -512,7 +555,7 @@ function importBackground(comp_id, composition_name, files_to_import){
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (files_to_import){
|
||||
for (i = 0; i < files_to_import.length; ++i){
|
||||
item = _importItem(files_to_import[i]);
|
||||
|
|
@ -524,8 +567,8 @@ function importBackground(comp_id, composition_name, files_to_import){
|
|||
if (!comp){
|
||||
folder = app.project.items.addFolder(composition_name);
|
||||
imported_ids.push(folder.id);
|
||||
comp = app.project.items.addComp(composition_name, item.width,
|
||||
item.height, item.pixelAspect,
|
||||
comp = app.project.items.addComp(composition_name, item.width,
|
||||
item.height, item.pixelAspect,
|
||||
1, 26.7); // hardcode defaults
|
||||
imported_ids.push(comp.id);
|
||||
comp.parentFolder = folder;
|
||||
|
|
@ -534,7 +577,7 @@ function importBackground(comp_id, composition_name, files_to_import){
|
|||
item.parentFolder = folder;
|
||||
|
||||
addItemAsLayerToComp(comp.id, item.id, comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
var item = {"name": comp.name,
|
||||
"id": folder.id,
|
||||
|
|
@ -545,19 +588,19 @@ function importBackground(comp_id, composition_name, files_to_import){
|
|||
function reloadBackground(comp_id, composition_name, files_to_import){
|
||||
/**
|
||||
* Reloads existing composition.
|
||||
*
|
||||
*
|
||||
* It deletes complete composition with encompassing folder, recreates
|
||||
* from scratch via 'importBackground' functionality.
|
||||
*
|
||||
*
|
||||
* Args:
|
||||
* comp_id (int): id of existing composition (null if new)
|
||||
* composition_name (str): used when new composition
|
||||
* composition_name (str): used when new composition
|
||||
* files_to_import (list): list of absolute paths to import and
|
||||
* add as layers
|
||||
*
|
||||
*
|
||||
* Returns:
|
||||
* (str): json representation (id, name, members)
|
||||
*
|
||||
*
|
||||
*/
|
||||
var imported_ids = []; // keep track of members of composition
|
||||
comp = app.project.itemByID(comp_id);
|
||||
|
|
@ -620,7 +663,7 @@ function reloadBackground(comp_id, composition_name, files_to_import){
|
|||
function _get_file_name(file_url){
|
||||
/**
|
||||
* Returns file name without extension from 'file_url'
|
||||
*
|
||||
*
|
||||
* Args:
|
||||
* file_url (str): full absolute url
|
||||
* Returns:
|
||||
|
|
@ -635,7 +678,7 @@ function _delete_obsolete_items(folder, new_filenames){
|
|||
/***
|
||||
* Goes through 'folder' and removes layers not in new
|
||||
* background
|
||||
*
|
||||
*
|
||||
* Args:
|
||||
* folder (FolderItem)
|
||||
* new_filenames (array): list of layer names in new bg
|
||||
|
|
@ -660,14 +703,14 @@ function _delete_obsolete_items(folder, new_filenames){
|
|||
function _importItem(file_url){
|
||||
/**
|
||||
* Imports 'file_url' as new FootageItem
|
||||
*
|
||||
*
|
||||
* Args:
|
||||
* file_url (str): file url with content
|
||||
* Returns:
|
||||
* (FootageItem)
|
||||
*/
|
||||
file_name = _get_file_name(file_url);
|
||||
|
||||
|
||||
//importFile prepared previously to return json
|
||||
item_json = importFile(file_url, file_name, JSON.stringify({"ImportAsType":"FOOTAGE"}));
|
||||
item_json = JSON.parse(item_json);
|
||||
|
|
@ -689,30 +732,42 @@ function isFileSequence (item){
|
|||
return false;
|
||||
}
|
||||
|
||||
function render(target_folder){
|
||||
function render(target_folder, comp_id){
|
||||
var out_dir = new Folder(target_folder);
|
||||
var out_dir = out_dir.fsName;
|
||||
for (i = 1; i <= app.project.renderQueue.numItems; ++i){
|
||||
var render_item = app.project.renderQueue.item(i);
|
||||
var om1 = app.project.renderQueue.item(i).outputModule(1);
|
||||
var file_name = File.decode( om1.file.name ).replace('℗', ''); // Name contains special character, space?
|
||||
var composition = render_item.comp;
|
||||
if (composition.id == comp_id){
|
||||
if (render_item.status == RQItemStatus.DONE){
|
||||
var new_item = render_item.duplicate();
|
||||
render_item.remove();
|
||||
render_item = new_item;
|
||||
}
|
||||
|
||||
render_item.render = true;
|
||||
|
||||
var om1 = app.project.renderQueue.item(i).outputModule(1);
|
||||
var file_name = File.decode( om1.file.name ).replace('℗', ''); // Name contains special character, space?
|
||||
|
||||
var omItem1_settable_str = app.project.renderQueue.item(i).outputModule(1).getSettings( GetSettingsFormat.STRING_SETTABLE );
|
||||
|
||||
var targetFolder = new Folder(target_folder);
|
||||
if (!targetFolder.exists) {
|
||||
targetFolder.create();
|
||||
}
|
||||
|
||||
om1.file = new File(targetFolder.fsName + '/' + file_name);
|
||||
}else{
|
||||
if (render_item.status != RQItemStatus.DONE){
|
||||
render_item.render = false;
|
||||
}
|
||||
}
|
||||
|
||||
var omItem1_settable_str = app.project.renderQueue.item(i).outputModule(1).getSettings( GetSettingsFormat.STRING_SETTABLE );
|
||||
|
||||
if (render_item.status == RQItemStatus.DONE){
|
||||
render_item.duplicate();
|
||||
render_item.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
var targetFolder = new Folder(target_folder);
|
||||
if (!targetFolder.exists) {
|
||||
targetFolder.create();
|
||||
}
|
||||
|
||||
om1.file = new File(targetFolder.fsName + '/' + file_name);
|
||||
}
|
||||
app.beginSuppressDialogs();
|
||||
app.project.renderQueue.render();
|
||||
app.endSuppressDialogs(false);
|
||||
}
|
||||
|
||||
function close(){
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from wsrpc_aiohttp import (
|
|||
WebSocketAsync
|
||||
)
|
||||
|
||||
from Qt import QtCore
|
||||
from qtpy import QtCore
|
||||
|
||||
from openpype.lib import Logger
|
||||
from openpype.pipeline import legacy_io
|
||||
|
|
@ -284,9 +284,6 @@ class AfterEffectsRoute(WebSocketRoute):
|
|||
return await self.socket.call('aftereffects.read')
|
||||
|
||||
# panel routes for tools
|
||||
async def creator_route(self):
|
||||
self._tool_route("creator")
|
||||
|
||||
async def workfiles_route(self):
|
||||
self._tool_route("workfiles")
|
||||
|
||||
|
|
@ -294,14 +291,11 @@ class AfterEffectsRoute(WebSocketRoute):
|
|||
self._tool_route("loader")
|
||||
|
||||
async def publish_route(self):
|
||||
self._tool_route("publish")
|
||||
self._tool_route("publisher")
|
||||
|
||||
async def sceneinventory_route(self):
|
||||
self._tool_route("sceneinventory")
|
||||
|
||||
async def subsetmanager_route(self):
|
||||
self._tool_route("subsetmanager")
|
||||
|
||||
async def experimental_tools_route(self):
|
||||
self._tool_route("experimental_tools")
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ import traceback
|
|||
import logging
|
||||
from functools import partial
|
||||
|
||||
from Qt import QtWidgets
|
||||
from qtpy import QtWidgets
|
||||
|
||||
from openpype.pipeline import install_host
|
||||
from openpype.modules import ModulesManager
|
||||
|
||||
from openpype.tools.utils import host_tools
|
||||
from openpype.tests.lib import is_in_tests
|
||||
from .launch_logic import ProcessLauncher, get_stub
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
@ -26,9 +27,10 @@ def safe_excepthook(*args):
|
|||
def main(*subprocess_args):
|
||||
sys.excepthook = safe_excepthook
|
||||
|
||||
from openpype.hosts.aftereffects import api
|
||||
from openpype.hosts.aftereffects.api import AfterEffectsHost
|
||||
|
||||
install_host(api)
|
||||
host = AfterEffectsHost()
|
||||
install_host(host)
|
||||
|
||||
os.environ["OPENPYPE_LOG_NO_COLORS"] = "False"
|
||||
app = QtWidgets.QApplication([])
|
||||
|
|
@ -46,7 +48,7 @@ def main(*subprocess_args):
|
|||
webpublisher_addon.headless_publish,
|
||||
log,
|
||||
"CloseAE",
|
||||
os.environ.get("IS_TEST")
|
||||
is_in_tests()
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -133,3 +135,32 @@ def get_background_layers(file_url):
|
|||
layer.get("filename")).
|
||||
replace("\\", "/"))
|
||||
return layers
|
||||
|
||||
|
||||
def get_asset_settings(asset_doc):
|
||||
"""Get settings on current asset from database.
|
||||
|
||||
Returns:
|
||||
dict: Scene data.
|
||||
|
||||
"""
|
||||
asset_data = asset_doc["data"]
|
||||
fps = asset_data.get("fps")
|
||||
frame_start = asset_data.get("frameStart")
|
||||
frame_end = asset_data.get("frameEnd")
|
||||
handle_start = asset_data.get("handleStart")
|
||||
handle_end = asset_data.get("handleEnd")
|
||||
resolution_width = asset_data.get("resolutionWidth")
|
||||
resolution_height = asset_data.get("resolutionHeight")
|
||||
duration = (frame_end - frame_start + 1) + handle_start + handle_end
|
||||
|
||||
return {
|
||||
"fps": fps,
|
||||
"frameStart": frame_start,
|
||||
"frameEnd": frame_end,
|
||||
"handleStart": handle_start,
|
||||
"handleEnd": handle_end,
|
||||
"resolutionWidth": resolution_width,
|
||||
"resolutionHeight": resolution_height,
|
||||
"duration": duration
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import os
|
||||
|
||||
from Qt import QtWidgets
|
||||
from qtpy import QtWidgets
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
|
@ -16,6 +16,13 @@ from openpype.pipeline import (
|
|||
from openpype.pipeline.load import any_outdated_containers
|
||||
import openpype.hosts.aftereffects
|
||||
|
||||
from openpype.host import (
|
||||
HostBase,
|
||||
IWorkfileHost,
|
||||
ILoadHost,
|
||||
IPublishHost
|
||||
)
|
||||
|
||||
from .launch_logic import get_stub, ConnectionNotEstablishedYet
|
||||
|
||||
log = Logger.get_logger(__name__)
|
||||
|
|
@ -30,27 +37,142 @@ LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
|
|||
CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
|
||||
|
||||
|
||||
def install():
|
||||
print("Installing Pype config...")
|
||||
class AfterEffectsHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
||||
name = "aftereffects"
|
||||
|
||||
pyblish.api.register_host("aftereffects")
|
||||
pyblish.api.register_plugin_path(PUBLISH_PATH)
|
||||
def __init__(self):
|
||||
self._stub = None
|
||||
super(AfterEffectsHost, self).__init__()
|
||||
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
register_creator_plugin_path(CREATE_PATH)
|
||||
log.info(PUBLISH_PATH)
|
||||
@property
|
||||
def stub(self):
|
||||
"""
|
||||
Handle pulling stub from PS to run operations on host
|
||||
Returns:
|
||||
(AEServerStub) or None
|
||||
"""
|
||||
if self._stub:
|
||||
return self._stub
|
||||
|
||||
pyblish.api.register_callback(
|
||||
"instanceToggled", on_pyblish_instance_toggled
|
||||
)
|
||||
try:
|
||||
stub = get_stub() # only after Photoshop is up
|
||||
except ConnectionNotEstablishedYet:
|
||||
print("Not connected yet, ignoring")
|
||||
return
|
||||
|
||||
register_event_callback("application.launched", application_launch)
|
||||
if not stub.get_active_document_name():
|
||||
return
|
||||
|
||||
self._stub = stub
|
||||
return self._stub
|
||||
|
||||
def uninstall():
|
||||
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
|
||||
deregister_loader_plugin_path(LOAD_PATH)
|
||||
deregister_creator_plugin_path(CREATE_PATH)
|
||||
def install(self):
|
||||
print("Installing Pype config...")
|
||||
|
||||
pyblish.api.register_host("aftereffects")
|
||||
pyblish.api.register_plugin_path(PUBLISH_PATH)
|
||||
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
register_creator_plugin_path(CREATE_PATH)
|
||||
log.info(PUBLISH_PATH)
|
||||
|
||||
pyblish.api.register_callback(
|
||||
"instanceToggled", on_pyblish_instance_toggled
|
||||
)
|
||||
|
||||
register_event_callback("application.launched", application_launch)
|
||||
|
||||
def get_workfile_extensions(self):
|
||||
return [".aep"]
|
||||
|
||||
def save_workfile(self, dst_path=None):
|
||||
self.stub.saveAs(dst_path, True)
|
||||
|
||||
def open_workfile(self, filepath):
|
||||
self.stub.open(filepath)
|
||||
|
||||
return True
|
||||
|
||||
def get_current_workfile(self):
|
||||
try:
|
||||
full_name = get_stub().get_active_document_full_name()
|
||||
if full_name and full_name != "null":
|
||||
return os.path.normpath(full_name).replace("\\", "/")
|
||||
except ValueError:
|
||||
print("Nothing opened")
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
def get_containers(self):
|
||||
return ls()
|
||||
|
||||
def get_context_data(self):
|
||||
meta = self.stub.get_metadata()
|
||||
for item in meta:
|
||||
if item.get("id") == "publish_context":
|
||||
item.pop("id")
|
||||
return item
|
||||
|
||||
return {}
|
||||
|
||||
def update_context_data(self, data, changes):
|
||||
item = data
|
||||
item["id"] = "publish_context"
|
||||
self.stub.imprint(item["id"], item)
|
||||
|
||||
# created instances section
|
||||
def list_instances(self):
|
||||
"""List all created instances from current workfile which
|
||||
will be published.
|
||||
|
||||
Pulls from File > File Info
|
||||
|
||||
For SubsetManager
|
||||
|
||||
Returns:
|
||||
(list) of dictionaries matching instances format
|
||||
"""
|
||||
stub = self.stub
|
||||
if not stub:
|
||||
return []
|
||||
|
||||
instances = []
|
||||
layers_meta = stub.get_metadata()
|
||||
|
||||
for instance in layers_meta:
|
||||
if instance.get("id") == "pyblish.avalon.instance":
|
||||
instances.append(instance)
|
||||
return instances
|
||||
|
||||
def remove_instance(self, instance):
|
||||
"""Remove instance from current workfile metadata.
|
||||
|
||||
Updates metadata of current file in File > File Info and removes
|
||||
icon highlight on group layer.
|
||||
|
||||
For SubsetManager
|
||||
|
||||
Args:
|
||||
instance (dict): instance representation from subsetmanager model
|
||||
"""
|
||||
stub = self.stub
|
||||
|
||||
if not stub:
|
||||
return
|
||||
|
||||
inst_id = instance.get("instance_id") or instance.get("uuid") # legacy
|
||||
if not inst_id:
|
||||
log.warning("No instance identifier for {}".format(instance))
|
||||
return
|
||||
|
||||
stub.remove_instance(inst_id)
|
||||
|
||||
if instance.get("members"):
|
||||
item = stub.get_item(instance["members"][0])
|
||||
if item:
|
||||
stub.rename_item(item.id,
|
||||
item.name.replace(stub.PUBLISH_ICON, ''))
|
||||
|
||||
|
||||
def application_launch():
|
||||
|
|
@ -63,35 +185,6 @@ def on_pyblish_instance_toggled(instance, old_value, new_value):
|
|||
instance[0].Visible = new_value
|
||||
|
||||
|
||||
def get_asset_settings(asset_doc):
|
||||
"""Get settings on current asset from database.
|
||||
|
||||
Returns:
|
||||
dict: Scene data.
|
||||
|
||||
"""
|
||||
asset_data = asset_doc["data"]
|
||||
fps = asset_data.get("fps")
|
||||
frame_start = asset_data.get("frameStart")
|
||||
frame_end = asset_data.get("frameEnd")
|
||||
handle_start = asset_data.get("handleStart")
|
||||
handle_end = asset_data.get("handleEnd")
|
||||
resolution_width = asset_data.get("resolutionWidth")
|
||||
resolution_height = asset_data.get("resolutionHeight")
|
||||
duration = (frame_end - frame_start + 1) + handle_start + handle_end
|
||||
|
||||
return {
|
||||
"fps": fps,
|
||||
"frameStart": frame_start,
|
||||
"frameEnd": frame_end,
|
||||
"handleStart": handle_start,
|
||||
"handleEnd": handle_end,
|
||||
"resolutionWidth": resolution_width,
|
||||
"resolutionHeight": resolution_height,
|
||||
"duration": duration
|
||||
}
|
||||
|
||||
|
||||
def ls():
|
||||
"""Yields containers from active AfterEffects document.
|
||||
|
||||
|
|
@ -191,102 +284,17 @@ def containerise(name,
|
|||
return comp
|
||||
|
||||
|
||||
# created instances section
|
||||
def list_instances():
|
||||
"""
|
||||
List all created instances from current workfile which
|
||||
will be published.
|
||||
def cache_and_get_instances(creator):
|
||||
"""Cache instances in shared data.
|
||||
|
||||
Pulls from File > File Info
|
||||
|
||||
For SubsetManager
|
||||
|
||||
Returns:
|
||||
(list) of dictionaries matching instances format
|
||||
"""
|
||||
stub = _get_stub()
|
||||
if not stub:
|
||||
return []
|
||||
|
||||
instances = []
|
||||
layers_meta = stub.get_metadata()
|
||||
|
||||
for instance in layers_meta:
|
||||
if instance.get("id") == "pyblish.avalon.instance":
|
||||
instances.append(instance)
|
||||
return instances
|
||||
|
||||
|
||||
def remove_instance(instance):
|
||||
"""
|
||||
Remove instance from current workfile metadata.
|
||||
|
||||
Updates metadata of current file in File > File Info and removes
|
||||
icon highlight on group layer.
|
||||
|
||||
For SubsetManager
|
||||
|
||||
Args:
|
||||
instance (dict): instance representation from subsetmanager model
|
||||
"""
|
||||
stub = _get_stub()
|
||||
|
||||
if not stub:
|
||||
return
|
||||
|
||||
inst_id = instance.get("instance_id") or instance.get("uuid") # legacy
|
||||
if not inst_id:
|
||||
log.warning("No instance identifier for {}".format(instance))
|
||||
return
|
||||
|
||||
stub.remove_instance(inst_id)
|
||||
|
||||
if instance.get("members"):
|
||||
item = stub.get_item(instance["members"][0])
|
||||
if item:
|
||||
stub.rename_item(item.id,
|
||||
item.name.replace(stub.PUBLISH_ICON, ''))
|
||||
|
||||
|
||||
# new publisher section
|
||||
def get_context_data():
|
||||
meta = _get_stub().get_metadata()
|
||||
for item in meta:
|
||||
if item.get("id") == "publish_context":
|
||||
item.pop("id")
|
||||
return item
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
def update_context_data(data, changes):
|
||||
item = data
|
||||
item["id"] = "publish_context"
|
||||
_get_stub().imprint(item["id"], item)
|
||||
|
||||
|
||||
def get_context_title():
|
||||
"""Returns title for Creator window"""
|
||||
|
||||
project_name = legacy_io.Session["AVALON_PROJECT"]
|
||||
asset_name = legacy_io.Session["AVALON_ASSET"]
|
||||
task_name = legacy_io.Session["AVALON_TASK"]
|
||||
return "{}/{}/{}".format(project_name, asset_name, task_name)
|
||||
|
||||
|
||||
def _get_stub():
|
||||
"""
|
||||
Handle pulling stub from PS to run operations on host
|
||||
Storing all instances as a list as legacy instances might be still present.
|
||||
Args:
|
||||
creator (Creator): Plugin which would like to get instances from host.
|
||||
Returns:
|
||||
(AEServerStub) or None
|
||||
List[]: list of all instances stored in metadata
|
||||
"""
|
||||
try:
|
||||
stub = get_stub() # only after Photoshop is up
|
||||
except ConnectionNotEstablishedYet:
|
||||
print("Not connected yet, ignoring")
|
||||
return
|
||||
|
||||
if not stub.get_active_document_name():
|
||||
return
|
||||
|
||||
return stub
|
||||
shared_key = "openpype.photoshop.instances"
|
||||
if shared_key not in creator.collection_shared_data:
|
||||
creator.collection_shared_data[shared_key] = \
|
||||
creator.host.list_instances()
|
||||
return creator.collection_shared_data[shared_key]
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
"""Host API required Work Files tool"""
|
||||
import os
|
||||
|
||||
from .launch_logic import get_stub
|
||||
|
||||
|
||||
def file_extensions():
|
||||
return [".aep"]
|
||||
|
||||
|
||||
def has_unsaved_changes():
|
||||
if _active_document():
|
||||
return not get_stub().is_saved()
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def save_file(filepath):
|
||||
get_stub().saveAs(filepath, True)
|
||||
|
||||
|
||||
def open_file(filepath):
|
||||
get_stub().open(filepath)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def current_file():
|
||||
try:
|
||||
full_name = get_stub().get_active_document_full_name()
|
||||
if full_name and full_name != "null":
|
||||
return os.path.normpath(full_name).replace("\\", "/")
|
||||
except ValueError:
|
||||
print("Nothing opened")
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def work_root(session):
|
||||
return os.path.normpath(session["AVALON_WORKDIR"]).replace("\\", "/")
|
||||
|
||||
|
||||
def _active_document():
|
||||
# TODO merge with current_file - even in extension
|
||||
document_name = None
|
||||
try:
|
||||
document_name = get_stub().get_active_document_name()
|
||||
except ValueError:
|
||||
print("Nothing opened")
|
||||
pass
|
||||
|
||||
return document_name
|
||||
|
|
@ -418,18 +418,18 @@ class AfterEffectsServerStub():
|
|||
|
||||
return self._handle_return(res)
|
||||
|
||||
def get_render_info(self):
|
||||
def get_render_info(self, comp_id):
|
||||
""" Get render queue info for render purposes
|
||||
|
||||
Returns:
|
||||
(AEItem): with 'file_name' field
|
||||
(list) of (AEItem): with 'file_name' field
|
||||
"""
|
||||
res = self.websocketserver.call(self.client.call
|
||||
('AfterEffects.get_render_info'))
|
||||
('AfterEffects.get_render_info',
|
||||
comp_id=comp_id))
|
||||
|
||||
records = self._to_records(self._handle_return(res))
|
||||
if records:
|
||||
return records.pop()
|
||||
return records
|
||||
|
||||
def get_audio_url(self, item_id):
|
||||
""" Get audio layer absolute url for comp
|
||||
|
|
@ -522,7 +522,7 @@ class AfterEffectsServerStub():
|
|||
if records:
|
||||
return records.pop()
|
||||
|
||||
def render(self, folder_url):
|
||||
def render(self, folder_url, comp_id):
|
||||
"""
|
||||
Render all renderqueueitem to 'folder_url'
|
||||
Args:
|
||||
|
|
@ -531,7 +531,8 @@ class AfterEffectsServerStub():
|
|||
"""
|
||||
res = self.websocketserver.call(self.client.call
|
||||
('AfterEffects.render',
|
||||
folder_url=folder_url))
|
||||
folder_url=folder_url,
|
||||
comp_id=comp_id))
|
||||
return self._handle_return(res)
|
||||
|
||||
def get_extension_version(self):
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
from openpype.hosts.aftereffects.plugins.create import create_legacy_render
|
||||
|
||||
|
||||
class CreateLocalRender(create_legacy_render.CreateRender):
|
||||
""" Creator to render locally.
|
||||
|
||||
Created only after default render on farm. So family 'render.local' is
|
||||
used for backward compatibility.
|
||||
"""
|
||||
|
||||
name = "renderDefault"
|
||||
label = "Render Locally"
|
||||
family = "renderLocal"
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
from openpype.pipeline import create
|
||||
from openpype.pipeline import CreatorError
|
||||
from openpype.hosts.aftereffects.api import (
|
||||
get_stub,
|
||||
list_instances
|
||||
)
|
||||
|
||||
|
||||
class CreateRender(create.LegacyCreator):
|
||||
"""Render folder for publish.
|
||||
|
||||
Creates subsets in format 'familyTaskSubsetname',
|
||||
eg 'renderCompositingMain'.
|
||||
|
||||
Create only single instance from composition at a time.
|
||||
"""
|
||||
|
||||
name = "renderDefault"
|
||||
label = "Render on Farm"
|
||||
family = "render"
|
||||
defaults = ["Main"]
|
||||
|
||||
def process(self):
|
||||
stub = get_stub() # only after After Effects is up
|
||||
items = []
|
||||
if (self.options or {}).get("useSelection"):
|
||||
items = stub.get_selected_items(
|
||||
comps=True, folders=False, footages=False
|
||||
)
|
||||
if len(items) > 1:
|
||||
raise CreatorError(
|
||||
"Please select only single composition at time."
|
||||
)
|
||||
|
||||
if not items:
|
||||
raise CreatorError((
|
||||
"Nothing to create. Select composition "
|
||||
"if 'useSelection' or create at least "
|
||||
"one composition."
|
||||
))
|
||||
|
||||
existing_subsets = [
|
||||
instance['subset'].lower()
|
||||
for instance in list_instances()
|
||||
]
|
||||
|
||||
item = items.pop()
|
||||
if self.name.lower() in existing_subsets:
|
||||
txt = "Instance with name \"{}\" already exists.".format(self.name)
|
||||
raise CreatorError(txt)
|
||||
|
||||
self.data["members"] = [item.id]
|
||||
self.data["uuid"] = item.id # for SubsetManager
|
||||
self.data["subset"] = (
|
||||
self.data["subset"]
|
||||
.replace(stub.PUBLISH_ICON, '')
|
||||
.replace(stub.LOADED_ICON, '')
|
||||
)
|
||||
|
||||
stub.imprint(item, self.data)
|
||||
stub.set_label_color(item.id, 14) # Cyan options 0 - 16
|
||||
stub.rename_item(item.id, stub.PUBLISH_ICON + self.data["subset"])
|
||||
|
|
@ -1,15 +1,24 @@
|
|||
import re
|
||||
|
||||
from openpype import resources
|
||||
from openpype.lib import BoolDef, UISeparatorDef
|
||||
from openpype.hosts.aftereffects import api
|
||||
from openpype.pipeline import (
|
||||
Creator,
|
||||
CreatedInstance,
|
||||
CreatorError,
|
||||
legacy_io,
|
||||
CreatorError
|
||||
)
|
||||
from openpype.hosts.aftereffects.api.pipeline import cache_and_get_instances
|
||||
from openpype.lib import prepare_template_data
|
||||
from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS
|
||||
|
||||
|
||||
class RenderCreator(Creator):
|
||||
"""Creates 'render' instance for publishing.
|
||||
|
||||
Result of 'render' instance is video or sequence of images for particular
|
||||
composition based of configuration in its RenderQueue.
|
||||
"""
|
||||
identifier = "render"
|
||||
label = "Render"
|
||||
family = "render"
|
||||
|
|
@ -24,11 +33,82 @@ class RenderCreator(Creator):
|
|||
["RenderCreator"]
|
||||
["defaults"])
|
||||
|
||||
def create(self, subset_name_from_ui, data, pre_create_data):
|
||||
stub = api.get_stub() # only after After Effects is up
|
||||
if pre_create_data.get("use_selection"):
|
||||
comps = stub.get_selected_items(
|
||||
comps=True, folders=False, footages=False
|
||||
)
|
||||
else:
|
||||
comps = stub.get_items(comps=True, folders=False, footages=False)
|
||||
|
||||
if not comps:
|
||||
raise CreatorError(
|
||||
"Nothing to create. Select composition "
|
||||
"if 'useSelection' or create at least "
|
||||
"one composition."
|
||||
)
|
||||
use_composition_name = (pre_create_data.get("use_composition_name") or
|
||||
len(comps) > 1)
|
||||
for comp in comps:
|
||||
if use_composition_name:
|
||||
if "{composition}" not in subset_name_from_ui.lower():
|
||||
subset_name_from_ui += "{Composition}"
|
||||
|
||||
composition_name = re.sub(
|
||||
"[^{}]+".format(SUBSET_NAME_ALLOWED_SYMBOLS),
|
||||
"",
|
||||
comp.name
|
||||
)
|
||||
|
||||
dynamic_fill = prepare_template_data({"composition":
|
||||
composition_name})
|
||||
subset_name = subset_name_from_ui.format(**dynamic_fill)
|
||||
data["composition_name"] = composition_name
|
||||
else:
|
||||
subset_name = subset_name_from_ui
|
||||
subset_name = re.sub(r"\{composition\}", '', subset_name,
|
||||
flags=re.IGNORECASE)
|
||||
|
||||
for inst in self.create_context.instances:
|
||||
if subset_name == inst.subset_name:
|
||||
raise CreatorError("{} already exists".format(
|
||||
inst.subset_name))
|
||||
|
||||
data["members"] = [comp.id]
|
||||
new_instance = CreatedInstance(self.family, subset_name, data,
|
||||
self)
|
||||
if "farm" in pre_create_data:
|
||||
use_farm = pre_create_data["farm"]
|
||||
new_instance.creator_attributes["farm"] = use_farm
|
||||
|
||||
api.get_stub().imprint(new_instance.id,
|
||||
new_instance.data_to_store())
|
||||
self._add_instance_to_context(new_instance)
|
||||
|
||||
stub.rename_item(comp.id, subset_name)
|
||||
|
||||
def get_default_variants(self):
|
||||
return self._default_variants
|
||||
|
||||
def get_instance_attr_defs(self):
|
||||
return [BoolDef("farm", label="Render on farm")]
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
output = [
|
||||
BoolDef("use_selection", default=True, label="Use selection"),
|
||||
BoolDef("use_composition_name",
|
||||
label="Use composition name in subset"),
|
||||
UISeparatorDef(),
|
||||
BoolDef("farm", label="Render on farm")
|
||||
]
|
||||
return output
|
||||
|
||||
def get_icon(self):
|
||||
return resources.get_openpype_splash_filepath()
|
||||
|
||||
def collect_instances(self):
|
||||
for instance_data in api.list_instances():
|
||||
for instance_data in cache_and_get_instances(self):
|
||||
# legacy instances have family=='render' or 'renderLocal', use them
|
||||
creator_id = (instance_data.get("creator_identifier") or
|
||||
instance_data.get("family", '').replace("Local", ''))
|
||||
|
|
@ -43,63 +123,65 @@ class RenderCreator(Creator):
|
|||
for created_inst, _changes in update_list:
|
||||
api.get_stub().imprint(created_inst.get("instance_id"),
|
||||
created_inst.data_to_store())
|
||||
subset_change = _changes.get("subset")
|
||||
if subset_change:
|
||||
api.get_stub().rename_item(created_inst.data["members"][0],
|
||||
subset_change.new_value)
|
||||
|
||||
def remove_instances(self, instances):
|
||||
for instance in instances:
|
||||
api.remove_instance(instance)
|
||||
self._remove_instance_from_context(instance)
|
||||
self.host.remove_instance(instance)
|
||||
|
||||
def create(self, subset_name, data, pre_create_data):
|
||||
stub = api.get_stub() # only after After Effects is up
|
||||
if pre_create_data.get("use_selection"):
|
||||
items = stub.get_selected_items(
|
||||
comps=True, folders=False, footages=False
|
||||
)
|
||||
else:
|
||||
items = stub.get_items(comps=True, folders=False, footages=False)
|
||||
|
||||
if len(items) > 1:
|
||||
raise CreatorError(
|
||||
"Please select only single composition at time."
|
||||
)
|
||||
if not items:
|
||||
raise CreatorError((
|
||||
"Nothing to create. Select composition "
|
||||
"if 'useSelection' or create at least "
|
||||
"one composition."
|
||||
))
|
||||
|
||||
for inst in self.create_context.instances:
|
||||
if subset_name == inst.subset_name:
|
||||
raise CreatorError("{} already exists".format(
|
||||
inst.subset_name))
|
||||
|
||||
data["members"] = [items[0].id]
|
||||
new_instance = CreatedInstance(self.family, subset_name, data, self)
|
||||
if "farm" in pre_create_data:
|
||||
use_farm = pre_create_data["farm"]
|
||||
new_instance.creator_attributes["farm"] = use_farm
|
||||
|
||||
api.get_stub().imprint(new_instance.id,
|
||||
new_instance.data_to_store())
|
||||
self._add_instance_to_context(new_instance)
|
||||
|
||||
def get_default_variants(self):
|
||||
return self._default_variants
|
||||
|
||||
def get_instance_attr_defs(self):
|
||||
return [BoolDef("farm", label="Render on farm")]
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
output = [
|
||||
BoolDef("use_selection", default=True, label="Use selection"),
|
||||
UISeparatorDef(),
|
||||
BoolDef("farm", label="Render on farm")
|
||||
]
|
||||
return output
|
||||
subset = instance.data["subset"]
|
||||
comp_id = instance.data["members"][0]
|
||||
comp = api.get_stub().get_item(comp_id)
|
||||
if comp:
|
||||
new_comp_name = comp.name.replace(subset, '')
|
||||
if not new_comp_name:
|
||||
new_comp_name = "dummyCompName"
|
||||
api.get_stub().rename_item(comp_id,
|
||||
new_comp_name)
|
||||
|
||||
def get_detail_description(self):
|
||||
return """Creator for Render instances"""
|
||||
return """Creator for Render instances
|
||||
|
||||
Main publishable item in AfterEffects will be of `render` family.
|
||||
Result of this item (instance) is picture sequence or video that could
|
||||
be a final delivery product or loaded and used in another DCCs.
|
||||
|
||||
Select single composition and create instance of 'render' family or
|
||||
turn off 'Use selection' to create instance for all compositions.
|
||||
|
||||
'Use composition name in subset' allows to explicitly add composition
|
||||
name into created subset name.
|
||||
|
||||
Position of composition name could be set in
|
||||
`project_settings/global/tools/creator/subset_name_profiles` with some
|
||||
form of '{composition}' placeholder.
|
||||
|
||||
Composition name will be used implicitly if multiple composition should
|
||||
be handled at same time.
|
||||
|
||||
If {composition} placeholder is not us 'subset_name_profiles'
|
||||
composition name will be capitalized and set at the end of subset name
|
||||
if necessary.
|
||||
|
||||
If composition name should be used, it will be cleaned up of characters
|
||||
that would cause an issue in published file names.
|
||||
"""
|
||||
|
||||
def get_dynamic_data(self, variant, task_name, asset_doc,
|
||||
project_name, host_name, instance):
|
||||
dynamic_data = {}
|
||||
if instance is not None:
|
||||
composition_name = instance.get("composition_name")
|
||||
if composition_name:
|
||||
dynamic_data["composition"] = composition_name
|
||||
else:
|
||||
dynamic_data["composition"] = "{composition}"
|
||||
|
||||
return dynamic_data
|
||||
|
||||
def _handle_legacy(self, instance_data):
|
||||
"""Converts old instances to new format."""
|
||||
|
|
@ -112,7 +194,7 @@ class RenderCreator(Creator):
|
|||
instance_data.pop("uuid")
|
||||
|
||||
if not instance_data.get("task"):
|
||||
instance_data["task"] = legacy_io.Session.get("AVALON_TASK")
|
||||
instance_data["task"] = self.create_context.get_current_task_name()
|
||||
|
||||
if not instance_data.get("creator_attributes"):
|
||||
is_old_farm = instance_data["family"] != "renderLocal"
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import openpype.hosts.aftereffects.api as api
|
|||
from openpype.client import get_asset_by_name
|
||||
from openpype.pipeline import (
|
||||
AutoCreator,
|
||||
CreatedInstance,
|
||||
legacy_io,
|
||||
CreatedInstance
|
||||
)
|
||||
from openpype.hosts.aftereffects.api.pipeline import cache_and_get_instances
|
||||
|
||||
|
||||
class AEWorkfileCreator(AutoCreator):
|
||||
|
|
@ -17,7 +17,7 @@ class AEWorkfileCreator(AutoCreator):
|
|||
return []
|
||||
|
||||
def collect_instances(self):
|
||||
for instance_data in api.list_instances():
|
||||
for instance_data in cache_and_get_instances(self):
|
||||
creator_id = instance_data.get("creator_identifier")
|
||||
if creator_id == self.identifier:
|
||||
subset_name = instance_data["subset"]
|
||||
|
|
@ -37,10 +37,11 @@ class AEWorkfileCreator(AutoCreator):
|
|||
existing_instance = instance
|
||||
break
|
||||
|
||||
project_name = legacy_io.Session["AVALON_PROJECT"]
|
||||
asset_name = legacy_io.Session["AVALON_ASSET"]
|
||||
task_name = legacy_io.Session["AVALON_TASK"]
|
||||
host_name = legacy_io.Session["AVALON_APP"]
|
||||
context = self.create_context
|
||||
project_name = context.get_current_project_name()
|
||||
asset_name = context.get_current_asset_name()
|
||||
task_name = context.get_current_task_name()
|
||||
host_name = context.host_name
|
||||
|
||||
if existing_instance is None:
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
|
|
@ -55,7 +56,7 @@ class AEWorkfileCreator(AutoCreator):
|
|||
}
|
||||
data.update(self.get_dynamic_data(
|
||||
self.default_variant, task_name, asset_doc,
|
||||
project_name, host_name
|
||||
project_name, host_name, None
|
||||
))
|
||||
|
||||
new_instance = CreatedInstance(
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class AERenderInstance(RenderInstance):
|
|||
stagingDir = attr.ib(default=None)
|
||||
app_version = attr.ib(default=None)
|
||||
publish_attributes = attr.ib(default={})
|
||||
file_name = attr.ib(default=None)
|
||||
file_names = attr.ib(default=[])
|
||||
|
||||
|
||||
class CollectAERender(publish.AbstractCollectRender):
|
||||
|
|
@ -64,14 +64,13 @@ class CollectAERender(publish.AbstractCollectRender):
|
|||
if family not in ["render", "renderLocal"]: # legacy
|
||||
continue
|
||||
|
||||
item_id = inst.data["members"][0]
|
||||
comp_id = int(inst.data["members"][0])
|
||||
|
||||
work_area_info = CollectAERender.get_stub().get_work_area(
|
||||
int(item_id))
|
||||
work_area_info = CollectAERender.get_stub().get_work_area(comp_id)
|
||||
|
||||
if not work_area_info:
|
||||
self.log.warning("Orphaned instance, deleting metadata")
|
||||
inst_id = inst.get("instance_id") or item_id
|
||||
inst_id = inst.get("instance_id") or str(comp_id)
|
||||
CollectAERender.get_stub().remove_instance(inst_id)
|
||||
continue
|
||||
|
||||
|
|
@ -84,9 +83,10 @@ class CollectAERender(publish.AbstractCollectRender):
|
|||
|
||||
task_name = inst.data.get("task") # legacy
|
||||
|
||||
render_q = CollectAERender.get_stub().get_render_info()
|
||||
render_q = CollectAERender.get_stub().get_render_info(comp_id)
|
||||
if not render_q:
|
||||
raise ValueError("No file extension set in Render Queue")
|
||||
render_item = render_q[0]
|
||||
|
||||
subset_name = inst.data["subset"]
|
||||
instance = AERenderInstance(
|
||||
|
|
@ -103,8 +103,8 @@ class CollectAERender(publish.AbstractCollectRender):
|
|||
setMembers='',
|
||||
publish=True,
|
||||
name=subset_name,
|
||||
resolutionWidth=render_q.width,
|
||||
resolutionHeight=render_q.height,
|
||||
resolutionWidth=render_item.width,
|
||||
resolutionHeight=render_item.height,
|
||||
pixelAspect=1,
|
||||
tileRendering=False,
|
||||
tilesX=0,
|
||||
|
|
@ -115,16 +115,16 @@ class CollectAERender(publish.AbstractCollectRender):
|
|||
fps=fps,
|
||||
app_version=app_version,
|
||||
publish_attributes=inst.data.get("publish_attributes", {}),
|
||||
file_name=render_q.file_name
|
||||
file_names=[item.file_name for item in render_q]
|
||||
)
|
||||
|
||||
comp = compositions_by_id.get(int(item_id))
|
||||
comp = compositions_by_id.get(comp_id)
|
||||
if not comp:
|
||||
raise ValueError("There is no composition for item {}".
|
||||
format(item_id))
|
||||
format(comp_id))
|
||||
instance.outputDir = self._get_output_dir(instance)
|
||||
instance.comp_name = comp.name
|
||||
instance.comp_id = item_id
|
||||
instance.comp_id = comp_id
|
||||
|
||||
is_local = "renderLocal" in inst.data["family"] # legacy
|
||||
if inst.data.get("creator_attributes"):
|
||||
|
|
@ -163,28 +163,30 @@ class CollectAERender(publish.AbstractCollectRender):
|
|||
start = render_instance.frameStart
|
||||
end = render_instance.frameEnd
|
||||
|
||||
_, ext = os.path.splitext(os.path.basename(render_instance.file_name))
|
||||
|
||||
base_dir = self._get_output_dir(render_instance)
|
||||
expected_files = []
|
||||
if "#" not in render_instance.file_name: # single frame (mov)W
|
||||
path = os.path.join(base_dir, "{}_{}_{}.{}".format(
|
||||
render_instance.asset,
|
||||
render_instance.subset,
|
||||
"v{:03d}".format(render_instance.version),
|
||||
ext.replace('.', '')
|
||||
))
|
||||
expected_files.append(path)
|
||||
else:
|
||||
for frame in range(start, end + 1):
|
||||
path = os.path.join(base_dir, "{}_{}_{}.{}.{}".format(
|
||||
for file_name in render_instance.file_names:
|
||||
_, ext = os.path.splitext(os.path.basename(file_name))
|
||||
ext = ext.replace('.', '')
|
||||
version_str = "v{:03d}".format(render_instance.version)
|
||||
if "#" not in file_name: # single frame (mov)W
|
||||
path = os.path.join(base_dir, "{}_{}_{}.{}".format(
|
||||
render_instance.asset,
|
||||
render_instance.subset,
|
||||
"v{:03d}".format(render_instance.version),
|
||||
str(frame).zfill(self.padding_width),
|
||||
ext.replace('.', '')
|
||||
version_str,
|
||||
ext
|
||||
))
|
||||
expected_files.append(path)
|
||||
else:
|
||||
for frame in range(start, end + 1):
|
||||
path = os.path.join(base_dir, "{}_{}_{}.{}.{}".format(
|
||||
render_instance.asset,
|
||||
render_instance.subset,
|
||||
version_str,
|
||||
str(frame).zfill(self.padding_width),
|
||||
ext
|
||||
))
|
||||
expected_files.append(path)
|
||||
return expected_files
|
||||
|
||||
def _get_output_dir(self, render_instance):
|
||||
|
|
|
|||
|
|
@ -21,41 +21,55 @@ class ExtractLocalRender(publish.Extractor):
|
|||
def process(self, instance):
|
||||
stub = get_stub()
|
||||
staging_dir = instance.data["stagingDir"]
|
||||
self.log.info("staging_dir::{}".format(staging_dir))
|
||||
self.log.debug("staging_dir::{}".format(staging_dir))
|
||||
|
||||
# pull file name from Render Queue Output module
|
||||
render_q = stub.get_render_info()
|
||||
stub.render(staging_dir)
|
||||
if not render_q:
|
||||
# pull file name collected value from Render Queue Output module
|
||||
if not instance.data["file_names"]:
|
||||
raise ValueError("No file extension set in Render Queue")
|
||||
_, ext = os.path.splitext(os.path.basename(render_q.file_name))
|
||||
ext = ext[1:]
|
||||
|
||||
first_file_path = None
|
||||
files = []
|
||||
self.log.info("files::{}".format(os.listdir(staging_dir)))
|
||||
for file_name in os.listdir(staging_dir):
|
||||
files.append(file_name)
|
||||
if first_file_path is None:
|
||||
first_file_path = os.path.join(staging_dir,
|
||||
file_name)
|
||||
comp_id = instance.data['comp_id']
|
||||
stub.render(staging_dir, comp_id)
|
||||
|
||||
resulting_files = files
|
||||
if len(files) == 1:
|
||||
resulting_files = files[0]
|
||||
representations = []
|
||||
for file_name in instance.data["file_names"]:
|
||||
_, ext = os.path.splitext(os.path.basename(file_name))
|
||||
ext = ext[1:]
|
||||
|
||||
repre_data = {
|
||||
"frameStart": instance.data["frameStart"],
|
||||
"frameEnd": instance.data["frameEnd"],
|
||||
"name": ext,
|
||||
"ext": ext,
|
||||
"files": resulting_files,
|
||||
"stagingDir": staging_dir
|
||||
}
|
||||
if instance.data["review"]:
|
||||
repre_data["tags"] = ["review"]
|
||||
first_file_path = None
|
||||
files = []
|
||||
for found_file_name in os.listdir(staging_dir):
|
||||
if not found_file_name.endswith(ext):
|
||||
continue
|
||||
|
||||
instance.data["representations"] = [repre_data]
|
||||
files.append(found_file_name)
|
||||
if first_file_path is None:
|
||||
first_file_path = os.path.join(staging_dir,
|
||||
found_file_name)
|
||||
|
||||
if not files:
|
||||
self.log.info("no files")
|
||||
return
|
||||
|
||||
# single file cannot be wrapped in array
|
||||
resulting_files = files
|
||||
if len(files) == 1:
|
||||
resulting_files = files[0]
|
||||
|
||||
repre_data = {
|
||||
"frameStart": instance.data["frameStart"],
|
||||
"frameEnd": instance.data["frameEnd"],
|
||||
"name": ext,
|
||||
"ext": ext,
|
||||
"files": resulting_files,
|
||||
"stagingDir": staging_dir
|
||||
}
|
||||
first_repre = not representations
|
||||
if instance.data["review"] and first_repre:
|
||||
repre_data["tags"] = ["review"]
|
||||
|
||||
representations.append(repre_data)
|
||||
|
||||
instance.data["representations"] = representations
|
||||
|
||||
ffmpeg_path = get_ffmpeg_tool_path("ffmpeg")
|
||||
# Generate thumbnail.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Context of the given subset doesn't match your current scene.
|
|||
|
||||
### How to repair?
|
||||
|
||||
You can fix this with "repair" button on the right.
|
||||
You can fix this with "repair" button on the right and refresh Publish at the bottom right.
|
||||
</description>
|
||||
<detail>
|
||||
### __Detailed Info__ (optional)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import json
|
||||
import pyblish.api
|
||||
from openpype.hosts.aftereffects.api import list_instances
|
||||
from openpype.hosts.aftereffects.api import AfterEffectsHost
|
||||
|
||||
|
||||
class PreCollectRender(pyblish.api.ContextPlugin):
|
||||
|
|
@ -25,7 +25,7 @@ class PreCollectRender(pyblish.api.ContextPlugin):
|
|||
self.log.debug("Not applicable for New Publisher, skip")
|
||||
return
|
||||
|
||||
for inst in list_instances():
|
||||
for inst in AfterEffectsHost().list_instances():
|
||||
if inst.get("creator_attributes"):
|
||||
raise ValueError("Instance created in New publisher, "
|
||||
"cannot be published in Pyblish.\n"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from pathlib import Path
|
|||
from types import ModuleType
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
from Qt import QtWidgets, QtCore
|
||||
from qtpy import QtWidgets, QtCore
|
||||
|
||||
import bpy
|
||||
import bpy.utils.previews
|
||||
|
|
|
|||
82
openpype/hosts/blender/plugins/load/import_workfile.py
Normal file
82
openpype/hosts/blender/plugins/load/import_workfile.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import bpy
|
||||
|
||||
from openpype.hosts.blender.api import plugin
|
||||
|
||||
|
||||
def append_workfile(context, fname, do_import):
|
||||
asset = context['asset']['name']
|
||||
subset = context['subset']['name']
|
||||
|
||||
group_name = plugin.asset_name(asset, subset)
|
||||
|
||||
# We need to preserve the original names of the scenes, otherwise,
|
||||
# if there are duplicate names in the current workfile, the imported
|
||||
# scenes will be renamed by Blender to avoid conflicts.
|
||||
original_scene_names = []
|
||||
|
||||
with bpy.data.libraries.load(fname) as (data_from, data_to):
|
||||
for attr in dir(data_to):
|
||||
if attr == "scenes":
|
||||
for scene in data_from.scenes:
|
||||
original_scene_names.append(scene)
|
||||
setattr(data_to, attr, getattr(data_from, attr))
|
||||
|
||||
current_scene = bpy.context.scene
|
||||
|
||||
for scene, s_name in zip(data_to.scenes, original_scene_names):
|
||||
scene.name = f"{group_name}_{s_name}"
|
||||
if do_import:
|
||||
collection = bpy.data.collections.new(f"{group_name}_{s_name}")
|
||||
for obj in scene.objects:
|
||||
collection.objects.link(obj)
|
||||
current_scene.collection.children.link(collection)
|
||||
for coll in scene.collection.children:
|
||||
collection.children.link(coll)
|
||||
|
||||
|
||||
class AppendBlendLoader(plugin.AssetLoader):
|
||||
"""Append workfile in Blender (unmanaged)
|
||||
|
||||
Warning:
|
||||
The loaded content will be unmanaged and is *not* visible in the
|
||||
scene inventory. It's purely intended to merge content into your scene
|
||||
so you could also use it as a new base.
|
||||
"""
|
||||
|
||||
representations = ["blend"]
|
||||
families = ["workfile"]
|
||||
|
||||
label = "Append Workfile"
|
||||
order = 9
|
||||
icon = "arrow-circle-down"
|
||||
color = "#775555"
|
||||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
append_workfile(context, self.fname, False)
|
||||
|
||||
# We do not containerize imported content, it remains unmanaged
|
||||
return
|
||||
|
||||
|
||||
class ImportBlendLoader(plugin.AssetLoader):
|
||||
"""Import workfile in the current Blender scene (unmanaged)
|
||||
|
||||
Warning:
|
||||
The loaded content will be unmanaged and is *not* visible in the
|
||||
scene inventory. It's purely intended to merge content into your scene
|
||||
so you could also use it as a new base.
|
||||
"""
|
||||
|
||||
representations = ["blend"]
|
||||
families = ["workfile"]
|
||||
|
||||
label = "Import Workfile"
|
||||
order = 9
|
||||
icon = "arrow-circle-down"
|
||||
color = "#775555"
|
||||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
append_workfile(context, self.fname, True)
|
||||
|
||||
# We do not containerize imported content, it remains unmanaged
|
||||
return
|
||||
|
|
@ -48,8 +48,14 @@ class BlendLayoutLoader(plugin.AssetLoader):
|
|||
bpy.data.objects.remove(obj)
|
||||
|
||||
def _remove_asset_and_library(self, asset_group):
|
||||
if not asset_group.get(AVALON_PROPERTY):
|
||||
return
|
||||
|
||||
libpath = asset_group.get(AVALON_PROPERTY).get('libpath')
|
||||
|
||||
if not libpath:
|
||||
return
|
||||
|
||||
# Check how many assets use the same library
|
||||
count = 0
|
||||
for obj in bpy.data.collections.get(AVALON_CONTAINERS).all_objects:
|
||||
|
|
@ -63,10 +69,12 @@ class BlendLayoutLoader(plugin.AssetLoader):
|
|||
# If it is the last object to use that library, remove it
|
||||
if count == 1:
|
||||
library = bpy.data.libraries.get(bpy.path.basename(libpath))
|
||||
bpy.data.libraries.remove(library)
|
||||
if library:
|
||||
bpy.data.libraries.remove(library)
|
||||
|
||||
def _process(
|
||||
self, libpath, asset_group, group_name, asset, representation, actions
|
||||
self, libpath, asset_group, group_name, asset, representation,
|
||||
actions, anim_instances
|
||||
):
|
||||
with bpy.data.libraries.load(
|
||||
libpath, link=True, relative=False
|
||||
|
|
@ -140,12 +148,12 @@ class BlendLayoutLoader(plugin.AssetLoader):
|
|||
elif local_obj.type == 'ARMATURE':
|
||||
plugin.prepare_data(local_obj.data)
|
||||
|
||||
if action is not None:
|
||||
if action:
|
||||
if local_obj.animation_data is None:
|
||||
local_obj.animation_data_create()
|
||||
local_obj.animation_data.action = action
|
||||
elif (local_obj.animation_data and
|
||||
local_obj.animation_data.action is not None):
|
||||
local_obj.animation_data.action):
|
||||
plugin.prepare_data(
|
||||
local_obj.animation_data.action)
|
||||
|
||||
|
|
@ -157,19 +165,26 @@ class BlendLayoutLoader(plugin.AssetLoader):
|
|||
t.id = local_obj
|
||||
|
||||
elif local_obj.type == 'EMPTY':
|
||||
creator_plugin = get_legacy_creator_by_name("CreateAnimation")
|
||||
if not creator_plugin:
|
||||
raise ValueError("Creator plugin \"CreateAnimation\" was "
|
||||
"not found.")
|
||||
if (not anim_instances or
|
||||
(anim_instances and
|
||||
local_obj.name not in anim_instances.keys())):
|
||||
avalon = local_obj.get(AVALON_PROPERTY)
|
||||
if avalon and avalon.get('family') == 'rig':
|
||||
creator_plugin = get_legacy_creator_by_name(
|
||||
"CreateAnimation")
|
||||
if not creator_plugin:
|
||||
raise ValueError(
|
||||
"Creator plugin \"CreateAnimation\" was "
|
||||
"not found.")
|
||||
|
||||
legacy_create(
|
||||
creator_plugin,
|
||||
name=local_obj.name.split(':')[-1] + "_animation",
|
||||
asset=asset,
|
||||
options={"useSelection": False,
|
||||
"asset_group": local_obj},
|
||||
data={"dependencies": representation}
|
||||
)
|
||||
legacy_create(
|
||||
creator_plugin,
|
||||
name=local_obj.name.split(':')[-1] + "_animation",
|
||||
asset=asset,
|
||||
options={"useSelection": False,
|
||||
"asset_group": local_obj},
|
||||
data={"dependencies": representation}
|
||||
)
|
||||
|
||||
if not local_obj.get(AVALON_PROPERTY):
|
||||
local_obj[AVALON_PROPERTY] = dict()
|
||||
|
|
@ -272,7 +287,8 @@ class BlendLayoutLoader(plugin.AssetLoader):
|
|||
avalon_container.objects.link(asset_group)
|
||||
|
||||
objects = self._process(
|
||||
libpath, asset_group, group_name, asset, representation, None)
|
||||
libpath, asset_group, group_name, asset, representation,
|
||||
None, None)
|
||||
|
||||
for child in asset_group.children:
|
||||
if child.get(AVALON_PROPERTY):
|
||||
|
|
@ -352,10 +368,20 @@ class BlendLayoutLoader(plugin.AssetLoader):
|
|||
return
|
||||
|
||||
actions = {}
|
||||
anim_instances = {}
|
||||
|
||||
for obj in asset_group.children:
|
||||
obj_meta = obj.get(AVALON_PROPERTY)
|
||||
if obj_meta.get('family') == 'rig':
|
||||
# Get animation instance
|
||||
collections = list(obj.users_collection)
|
||||
for c in collections:
|
||||
avalon = c.get(AVALON_PROPERTY)
|
||||
if avalon and avalon.get('family') == 'animation':
|
||||
anim_instances[obj.name] = c.name
|
||||
break
|
||||
|
||||
# Get armature's action
|
||||
rig = None
|
||||
for child in obj.children:
|
||||
if child.type == 'ARMATURE':
|
||||
|
|
@ -384,9 +410,26 @@ class BlendLayoutLoader(plugin.AssetLoader):
|
|||
# If it is the last object to use that library, remove it
|
||||
if count == 1:
|
||||
library = bpy.data.libraries.get(bpy.path.basename(group_libpath))
|
||||
bpy.data.libraries.remove(library)
|
||||
if library:
|
||||
bpy.data.libraries.remove(library)
|
||||
|
||||
self._process(str(libpath), asset_group, object_name, actions)
|
||||
asset = container.get("asset_name").split("_")[0]
|
||||
|
||||
self._process(
|
||||
str(libpath), asset_group, object_name, asset,
|
||||
str(representation.get("_id")), actions, anim_instances
|
||||
)
|
||||
|
||||
# Link the new objects to the animation collection
|
||||
for inst in anim_instances.keys():
|
||||
try:
|
||||
obj = bpy.data.objects[inst]
|
||||
bpy.data.collections[anim_instances[inst]].objects.link(obj)
|
||||
except KeyError:
|
||||
self.log.info(f"Object {inst} does not exist anymore.")
|
||||
coll = bpy.data.collections.get(anim_instances[inst])
|
||||
if (coll):
|
||||
bpy.data.collections.remove(coll)
|
||||
|
||||
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
|
||||
for child in asset_group.children:
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin):
|
|||
order = ValidateContentsOrder
|
||||
hosts = ["blender"]
|
||||
families = ["camera"]
|
||||
version = (0, 1, 0)
|
||||
label = "Zero Keyframe"
|
||||
actions = [openpype.hosts.blender.api.action.SelectInvalidAction]
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ class ValidateMeshHasUvs(pyblish.api.InstancePlugin):
|
|||
order = ValidateContentsOrder
|
||||
hosts = ["blender"]
|
||||
families = ["model"]
|
||||
category = "geometry"
|
||||
label = "Mesh Has UV's"
|
||||
actions = [openpype.hosts.blender.api.action.SelectInvalidAction]
|
||||
optional = True
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator):
|
|||
order = ValidateContentsOrder
|
||||
hosts = ["blender"]
|
||||
families = ["model"]
|
||||
category = "geometry"
|
||||
label = "Mesh No Negative Scale"
|
||||
actions = [openpype.hosts.blender.api.action.SelectInvalidAction]
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ class ValidateNoColonsInName(pyblish.api.InstancePlugin):
|
|||
order = ValidateContentsOrder
|
||||
hosts = ["blender"]
|
||||
families = ["model", "rig"]
|
||||
version = (0, 1, 0)
|
||||
label = "No Colons in names"
|
||||
actions = [openpype.hosts.blender.api.action.SelectInvalidAction]
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ class ValidateTransformZero(pyblish.api.InstancePlugin):
|
|||
order = ValidateContentsOrder
|
||||
hosts = ["blender"]
|
||||
families = ["model"]
|
||||
version = (0, 1, 0)
|
||||
label = "Transform Zero"
|
||||
actions = [openpype.hosts.blender.api.action.SelectInvalidAction]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import pyblish.api
|
||||
import argparse
|
||||
import sys
|
||||
from pprint import pformat
|
||||
|
||||
|
|
@ -11,20 +10,40 @@ class CollectCelactionCliKwargs(pyblish.api.Collector):
|
|||
order = pyblish.api.Collector.order - 0.1
|
||||
|
||||
def process(self, context):
|
||||
parser = argparse.ArgumentParser(prog="celaction")
|
||||
parser.add_argument("--currentFile",
|
||||
help="Pass file to Context as `currentFile`")
|
||||
parser.add_argument("--chunk",
|
||||
help=("Render chanks on farm"))
|
||||
parser.add_argument("--frameStart",
|
||||
help=("Start of frame range"))
|
||||
parser.add_argument("--frameEnd",
|
||||
help=("End of frame range"))
|
||||
parser.add_argument("--resolutionWidth",
|
||||
help=("Width of resolution"))
|
||||
parser.add_argument("--resolutionHeight",
|
||||
help=("Height of resolution"))
|
||||
passing_kwargs = parser.parse_args(sys.argv[1:]).__dict__
|
||||
args = list(sys.argv[1:])
|
||||
self.log.info(str(args))
|
||||
missing_kwargs = []
|
||||
passing_kwargs = {}
|
||||
for key in (
|
||||
"chunk",
|
||||
"frameStart",
|
||||
"frameEnd",
|
||||
"resolutionWidth",
|
||||
"resolutionHeight",
|
||||
"currentFile",
|
||||
):
|
||||
arg_key = f"--{key}"
|
||||
if arg_key not in args:
|
||||
missing_kwargs.append(key)
|
||||
continue
|
||||
arg_idx = args.index(arg_key)
|
||||
args.pop(arg_idx)
|
||||
if key != "currentFile":
|
||||
value = args.pop(arg_idx)
|
||||
else:
|
||||
path_parts = []
|
||||
while arg_idx < len(args):
|
||||
path_parts.append(args.pop(arg_idx))
|
||||
value = " ".join(path_parts).strip('"')
|
||||
|
||||
passing_kwargs[key] = value
|
||||
|
||||
if missing_kwargs:
|
||||
raise RuntimeError("Missing arguments {}".format(
|
||||
", ".join(
|
||||
[f'"{key}"' for key in missing_kwargs]
|
||||
)
|
||||
))
|
||||
|
||||
self.log.info("Storing kwargs ...")
|
||||
self.log.debug("_ passing_kwargs: {}".format(pformat(passing_kwargs)))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import os
|
||||
from Qt import QtWidgets
|
||||
from qtpy import QtWidgets
|
||||
from copy import deepcopy
|
||||
from pprint import pformat
|
||||
from openpype.tools.utils.host_tools import HostToolsHelper
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ from copy import deepcopy
|
|||
from xml.etree import ElementTree as ET
|
||||
|
||||
import qargparse
|
||||
from Qt import QtCore, QtWidgets
|
||||
from qtpy import QtCore, QtWidgets
|
||||
|
||||
from openpype import style
|
||||
from openpype.lib import Logger
|
||||
from openpype.lib import Logger, StringTemplate
|
||||
from openpype.pipeline import LegacyCreator, LoaderPlugin
|
||||
from openpype.settings import get_current_project_settings
|
||||
|
||||
|
|
@ -702,6 +702,37 @@ class ClipLoader(LoaderPlugin):
|
|||
|
||||
_mapping = None
|
||||
|
||||
def apply_settings(cls, project_settings, system_settings):
|
||||
|
||||
plugin_type_settings = (
|
||||
project_settings
|
||||
.get("flame", {})
|
||||
.get("load", {})
|
||||
)
|
||||
|
||||
if not plugin_type_settings:
|
||||
return
|
||||
|
||||
plugin_name = cls.__name__
|
||||
|
||||
plugin_settings = None
|
||||
# Look for plugin settings in host specific settings
|
||||
if plugin_name in plugin_type_settings:
|
||||
plugin_settings = plugin_type_settings[plugin_name]
|
||||
|
||||
if not plugin_settings:
|
||||
return
|
||||
|
||||
print(">>> We have preset for {}".format(plugin_name))
|
||||
for option, value in plugin_settings.items():
|
||||
if option == "enabled" and value is False:
|
||||
print(" - is disabled by preset")
|
||||
elif option == "representations":
|
||||
continue
|
||||
else:
|
||||
print(" - setting `{}`: `{}`".format(option, value))
|
||||
setattr(cls, option, value)
|
||||
|
||||
def get_colorspace(self, context):
|
||||
"""Get colorspace name
|
||||
|
||||
|
|
@ -775,6 +806,11 @@ class OpenClipSolver(flib.MediaInfoFile):
|
|||
self.feed_colorspace = feed_data.get("colorspace")
|
||||
self.log.debug("feed_version_name: {}".format(self.feed_version_name))
|
||||
|
||||
# layer rename variables
|
||||
self.layer_rename_template = feed_data["layer_rename_template"]
|
||||
self.layer_rename_patterns = feed_data["layer_rename_patterns"]
|
||||
self.context_data = feed_data["context_data"]
|
||||
|
||||
# derivate other feed variables
|
||||
self.feed_basename = os.path.basename(feed_path)
|
||||
self.feed_dir = os.path.dirname(feed_path)
|
||||
|
|
@ -813,9 +849,11 @@ class OpenClipSolver(flib.MediaInfoFile):
|
|||
|
||||
def _create_new_open_clip(self):
|
||||
self.log.info("Building new openClip")
|
||||
self.log.debug(">> self.clip_data: {}".format(self.clip_data))
|
||||
|
||||
for tmp_xml_track in self.clip_data.iter("track"):
|
||||
# solve track (layer) name
|
||||
self._rename_track_name(tmp_xml_track)
|
||||
|
||||
tmp_xml_feeds = tmp_xml_track.find('feeds')
|
||||
tmp_xml_feeds.set('currentVersion', self.feed_version_name)
|
||||
|
||||
|
|
@ -850,6 +888,48 @@ class OpenClipSolver(flib.MediaInfoFile):
|
|||
if uid == track_uid:
|
||||
return xml_track
|
||||
|
||||
def _rename_track_name(self, xml_track_data):
|
||||
layer_uid = xml_track_data.get("uid")
|
||||
name_obj = xml_track_data.find("name")
|
||||
layer_name = name_obj.text
|
||||
|
||||
if (
|
||||
self.layer_rename_patterns
|
||||
and not any(
|
||||
re.search(lp_.lower(), layer_name.lower())
|
||||
for lp_ in self.layer_rename_patterns
|
||||
)
|
||||
):
|
||||
return
|
||||
|
||||
formating_data = self._update_formating_data(
|
||||
layerName=layer_name,
|
||||
layerUID=layer_uid
|
||||
)
|
||||
name_obj.text = StringTemplate(
|
||||
self.layer_rename_template
|
||||
).format(formating_data)
|
||||
|
||||
def _update_formating_data(self, **kwargs):
|
||||
""" Updating formating data for layer rename
|
||||
|
||||
Attributes:
|
||||
key=value (optional): will be included to formating data
|
||||
as {key: value}
|
||||
Returns:
|
||||
dict: anatomy context data for formating
|
||||
"""
|
||||
self.log.debug(">> self.clip_data: {}".format(self.clip_data))
|
||||
clip_name_obj = self.clip_data.find("name")
|
||||
data = {
|
||||
"originalBasename": clip_name_obj.text
|
||||
}
|
||||
# include version context data
|
||||
data.update(self.context_data)
|
||||
# include input kwargs data
|
||||
data.update(kwargs)
|
||||
return data
|
||||
|
||||
def _update_open_clip(self):
|
||||
self.log.info("Updating openClip ..")
|
||||
|
||||
|
|
@ -857,11 +937,12 @@ class OpenClipSolver(flib.MediaInfoFile):
|
|||
out_xml = out_xml.getroot()
|
||||
|
||||
self.log.debug(">> out_xml: {}".format(out_xml))
|
||||
self.log.debug(">> self.clip_data: {}".format(self.clip_data))
|
||||
|
||||
# loop tmp tracks
|
||||
updated_any = False
|
||||
for tmp_xml_track in self.clip_data.iter("track"):
|
||||
# solve track (layer) name
|
||||
self._rename_track_name(tmp_xml_track)
|
||||
|
||||
# get tmp track uid
|
||||
tmp_track_uid = tmp_xml_track.get("uid")
|
||||
self.log.debug(">> tmp_track_uid: {}".format(tmp_track_uid))
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
from copy import deepcopy
|
||||
import os
|
||||
import flame
|
||||
from pprint import pformat
|
||||
import openpype.hosts.flame.api as opfapi
|
||||
from openpype.lib import StringTemplate
|
||||
from openpype.lib.transcoding import (
|
||||
VIDEO_EXTENSIONS,
|
||||
IMAGE_EXTENSIONS
|
||||
)
|
||||
|
||||
|
||||
class LoadClip(opfapi.ClipLoader):
|
||||
|
|
@ -13,7 +18,10 @@ class LoadClip(opfapi.ClipLoader):
|
|||
"""
|
||||
|
||||
families = ["render2d", "source", "plate", "render", "review"]
|
||||
representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264"]
|
||||
representations = ["*"]
|
||||
extensions = set(
|
||||
ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS)
|
||||
)
|
||||
|
||||
label = "Load as clip"
|
||||
order = -10
|
||||
|
|
@ -25,6 +33,14 @@ class LoadClip(opfapi.ClipLoader):
|
|||
reel_name = "Loaded"
|
||||
clip_name_template = "{asset}_{subset}<_{output}>"
|
||||
|
||||
""" Anatomy keys from version context data and dynamically added:
|
||||
- {layerName} - original layer name token
|
||||
- {layerUID} - original layer UID token
|
||||
- {originalBasename} - original clip name taken from file
|
||||
"""
|
||||
layer_rename_template = "{asset}_{subset}<_{output}>"
|
||||
layer_rename_patterns = []
|
||||
|
||||
def load(self, context, name, namespace, options):
|
||||
|
||||
# get flame objects
|
||||
|
|
@ -38,8 +54,16 @@ class LoadClip(opfapi.ClipLoader):
|
|||
version_name = version.get("name", None)
|
||||
colorspace = self.get_colorspace(context)
|
||||
|
||||
# in case output is not in context replace key to representation
|
||||
if not context["representation"]["context"].get("output"):
|
||||
self.clip_name_template = self.clip_name_template.replace(
|
||||
"output", "representation")
|
||||
self.layer_rename_template = self.layer_rename_template.replace(
|
||||
"output", "representation")
|
||||
|
||||
formating_data = deepcopy(context["representation"]["context"])
|
||||
clip_name = StringTemplate(self.clip_name_template).format(
|
||||
context["representation"]["context"])
|
||||
formating_data)
|
||||
|
||||
# convert colorspace with ocio to flame mapping
|
||||
# in imageio flame section
|
||||
|
|
@ -62,6 +86,9 @@ class LoadClip(opfapi.ClipLoader):
|
|||
"path": self.fname.replace("\\", "/"),
|
||||
"colorspace": colorspace,
|
||||
"version": "v{:0>3}".format(version_name),
|
||||
"layer_rename_template": self.layer_rename_template,
|
||||
"layer_rename_patterns": self.layer_rename_patterns,
|
||||
"context_data": formating_data
|
||||
}
|
||||
self.log.debug(pformat(
|
||||
loading_context
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ import flame
|
|||
from pprint import pformat
|
||||
import openpype.hosts.flame.api as opfapi
|
||||
from openpype.lib import StringTemplate
|
||||
|
||||
from openpype.lib.transcoding import (
|
||||
VIDEO_EXTENSIONS,
|
||||
IMAGE_EXTENSIONS
|
||||
)
|
||||
|
||||
class LoadClipBatch(opfapi.ClipLoader):
|
||||
"""Load a subset to timeline as clip
|
||||
|
|
@ -14,7 +17,10 @@ class LoadClipBatch(opfapi.ClipLoader):
|
|||
"""
|
||||
|
||||
families = ["render2d", "source", "plate", "render", "review"]
|
||||
representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264"]
|
||||
representations = ["*"]
|
||||
extensions = set(
|
||||
ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS)
|
||||
)
|
||||
|
||||
label = "Load as clip to current batch"
|
||||
order = -10
|
||||
|
|
@ -25,6 +31,14 @@ class LoadClipBatch(opfapi.ClipLoader):
|
|||
reel_name = "OP_LoadedReel"
|
||||
clip_name_template = "{batch}_{asset}_{subset}<_{output}>"
|
||||
|
||||
""" Anatomy keys from version context data and dynamically added:
|
||||
- {layerName} - original layer name token
|
||||
- {layerUID} - original layer UID token
|
||||
- {originalBasename} - original clip name taken from file
|
||||
"""
|
||||
layer_rename_template = "{asset}_{subset}<_{output}>"
|
||||
layer_rename_patterns = []
|
||||
|
||||
def load(self, context, name, namespace, options):
|
||||
|
||||
# get flame objects
|
||||
|
|
@ -39,7 +53,10 @@ class LoadClipBatch(opfapi.ClipLoader):
|
|||
|
||||
# in case output is not in context replace key to representation
|
||||
if not context["representation"]["context"].get("output"):
|
||||
self.clip_name_template.replace("output", "representation")
|
||||
self.clip_name_template = self.clip_name_template.replace(
|
||||
"output", "representation")
|
||||
self.layer_rename_template = self.layer_rename_template.replace(
|
||||
"output", "representation")
|
||||
|
||||
formating_data = deepcopy(context["representation"]["context"])
|
||||
formating_data["batch"] = self.batch.name.get_value()
|
||||
|
|
@ -69,6 +86,9 @@ class LoadClipBatch(opfapi.ClipLoader):
|
|||
"path": self.fname.replace("\\", "/"),
|
||||
"colorspace": colorspace,
|
||||
"version": "v{:0>3}".format(version_name),
|
||||
"layer_rename_template": self.layer_rename_template,
|
||||
"layer_rename_patterns": self.layer_rename_patterns,
|
||||
"context_data": formating_data
|
||||
}
|
||||
self.log.debug(pformat(
|
||||
loading_context
|
||||
|
|
|
|||
|
|
@ -143,6 +143,9 @@ class ExtractSubsetResources(publish.Extractor):
|
|||
# create staging dir path
|
||||
staging_dir = self.staging_dir(instance)
|
||||
|
||||
# append staging dir for later cleanup
|
||||
instance.context.data["cleanupFullPaths"].append(staging_dir)
|
||||
|
||||
# add default preset type for thumbnail and reviewable video
|
||||
# update them with settings and override in case the same
|
||||
# are found in there
|
||||
|
|
@ -548,30 +551,3 @@ class ExtractSubsetResources(publish.Extractor):
|
|||
"Path `{}` is containing more that one clip".format(path)
|
||||
)
|
||||
return clips[0]
|
||||
|
||||
def staging_dir(self, instance):
|
||||
"""Provide a temporary directory in which to store extracted files
|
||||
|
||||
Upon calling this method the staging directory is stored inside
|
||||
the instance.data['stagingDir']
|
||||
"""
|
||||
staging_dir = instance.data.get('stagingDir', None)
|
||||
openpype_temp_dir = os.getenv("OPENPYPE_TEMP_DIR")
|
||||
|
||||
if not staging_dir:
|
||||
if openpype_temp_dir and os.path.exists(openpype_temp_dir):
|
||||
staging_dir = os.path.normpath(
|
||||
tempfile.mkdtemp(
|
||||
prefix="pyblish_tmp_",
|
||||
dir=openpype_temp_dir
|
||||
)
|
||||
)
|
||||
else:
|
||||
staging_dir = os.path.normpath(
|
||||
tempfile.mkdtemp(prefix="pyblish_tmp_")
|
||||
)
|
||||
instance.data['stagingDir'] = staging_dir
|
||||
|
||||
instance.context.data["cleanupFullPaths"].append(staging_dir)
|
||||
|
||||
return staging_dir
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from Qt import QtWidgets, QtCore
|
||||
from qtpy import QtWidgets, QtCore
|
||||
|
||||
import uiwidgets
|
||||
import app_utils
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from Qt import QtWidgets, QtCore
|
||||
from qtpy import QtWidgets, QtCore
|
||||
|
||||
|
||||
class FlameLabel(QtWidgets.QLabel):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import print_function
|
||||
import sys
|
||||
from Qt import QtWidgets
|
||||
from qtpy import QtWidgets
|
||||
from pprint import pformat
|
||||
import atexit
|
||||
|
||||
|
|
|
|||
|
|
@ -210,7 +210,8 @@ def switch_item(container,
|
|||
if any(not x for x in [asset_name, subset_name, representation_name]):
|
||||
repre_id = container["representation"]
|
||||
representation = get_representation_by_id(project_name, repre_id)
|
||||
repre_parent_docs = get_representation_parents(representation)
|
||||
repre_parent_docs = get_representation_parents(
|
||||
project_name, representation)
|
||||
if repre_parent_docs:
|
||||
version, subset, asset, _ = repre_parent_docs
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import sys
|
||||
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
from qtpy import QtWidgets, QtCore, QtGui
|
||||
|
||||
from openpype.tools.utils import host_tools
|
||||
from openpype.style import load_stylesheet
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import sys
|
|||
import logging
|
||||
|
||||
import pyblish.api
|
||||
from Qt import QtCore
|
||||
from qtpy import QtCore
|
||||
|
||||
from openpype.lib import (
|
||||
Logger,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
from Qt import QtCore
|
||||
from qtpy import QtCore
|
||||
|
||||
|
||||
class PulseThread(QtCore.QThread):
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import importlib
|
|||
|
||||
|
||||
try:
|
||||
from Qt import QtWidgets # noqa: F401
|
||||
from Qt import __binding__
|
||||
print(f"Qt binding: {__binding__}")
|
||||
mod = importlib.import_module(__binding__)
|
||||
from qtpy import API_NAME
|
||||
|
||||
print(f"Qt binding: {API_NAME}")
|
||||
mod = importlib.import_module(API_NAME)
|
||||
print(f"Qt path: {mod.__file__}")
|
||||
print("Qt library found, nothing to do..")
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import sys
|
|||
import glob
|
||||
import logging
|
||||
|
||||
from Qt import QtWidgets, QtCore
|
||||
from qtpy import QtWidgets, QtCore
|
||||
|
||||
import qtawesome as qta
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class FusionPrelaunch(PreLaunchHook):
|
|||
"Make sure the environment in fusion settings has "
|
||||
"'FUSION_PYTHON3_HOME' set correctly and make sure "
|
||||
"Python 3 is installed in the given path."
|
||||
f"\n\nPYTHON36: {fusion_python3_home}"
|
||||
f"\n\nPYTHON PATH: {fusion_python3_home}"
|
||||
)
|
||||
|
||||
self.log.info(f"Setting {py3_var}: '{py3_dir}'...")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from Qt import QtGui, QtWidgets
|
||||
from qtpy import QtGui, QtWidgets
|
||||
|
||||
from openpype.pipeline import InventoryAction
|
||||
from openpype import style
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class FusionSetFrameRangeLoader(load.LoaderPlugin):
|
|||
"pointcache",
|
||||
"render"]
|
||||
representations = ["*"]
|
||||
extensions = {"*"}
|
||||
|
||||
label = "Set frame range"
|
||||
order = 11
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ class FusionLoadAlembicMesh(load.LoaderPlugin):
|
|||
"""Load Alembic mesh into Fusion"""
|
||||
|
||||
families = ["pointcache", "model"]
|
||||
representations = ["abc"]
|
||||
representations = ["*"]
|
||||
extensions = {"abc"}
|
||||
|
||||
label = "Load alembic mesh"
|
||||
order = -10
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ class FusionLoadFBXMesh(load.LoaderPlugin):
|
|||
"""Load FBX mesh into Fusion"""
|
||||
|
||||
families = ["*"]
|
||||
representations = ["fbx"]
|
||||
representations = ["*"]
|
||||
extensions = {"fbx"}
|
||||
|
||||
label = "Load FBX mesh"
|
||||
order = -10
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ from openpype.hosts.fusion.api import (
|
|||
get_current_comp,
|
||||
comp_lock_and_undo_chunk
|
||||
)
|
||||
from openpype.lib.transcoding import (
|
||||
IMAGE_EXTENSIONS,
|
||||
VIDEO_EXTENSIONS
|
||||
)
|
||||
|
||||
comp = get_current_comp()
|
||||
|
||||
|
|
@ -129,6 +133,9 @@ class FusionLoadSequence(load.LoaderPlugin):
|
|||
|
||||
families = ["imagesequence", "review", "render", "plate"]
|
||||
representations = ["*"]
|
||||
extensions = set(
|
||||
ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS)
|
||||
)
|
||||
|
||||
label = "Load sequence"
|
||||
order = -10
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ class CollectInstances(pyblish.api.ContextPlugin):
|
|||
"outputDir": os.path.dirname(path),
|
||||
"ext": ext, # todo: should be redundant
|
||||
"label": label,
|
||||
"task": context.data["task"],
|
||||
"frameStart": context.data["frameStart"],
|
||||
"frameEnd": context.data["frameEnd"],
|
||||
"frameStartHandle": context.data["frameStartHandle"],
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import os
|
||||
from pprint import pformat
|
||||
|
||||
import pyblish.api
|
||||
from openpype.hosts.fusion.api import comp_lock_and_undo_chunk
|
||||
|
||||
|
|
@ -23,23 +21,53 @@ class Fusionlocal(pyblish.api.InstancePlugin):
|
|||
# This plug-in runs only once and thus assumes all instances
|
||||
# currently will render the same frame range
|
||||
context = instance.context
|
||||
key = "__hasRun{}".format(self.__class__.__name__)
|
||||
key = f"__hasRun{self.__class__.__name__}"
|
||||
if context.data.get(key, False):
|
||||
return
|
||||
else:
|
||||
context.data[key] = True
|
||||
|
||||
current_comp = context.data["currentComp"]
|
||||
context.data[key] = True
|
||||
|
||||
self.render_once(context)
|
||||
|
||||
frame_start = context.data["frameStartHandle"]
|
||||
frame_end = context.data["frameEndHandle"]
|
||||
path = instance.data["path"]
|
||||
output_dir = instance.data["outputDir"]
|
||||
|
||||
ext = os.path.splitext(os.path.basename(path))[-1]
|
||||
basename = os.path.basename(path)
|
||||
head, ext = os.path.splitext(basename)
|
||||
files = [
|
||||
f"{head}{str(frame).zfill(4)}{ext}"
|
||||
for frame in range(frame_start, frame_end + 1)
|
||||
]
|
||||
repre = {
|
||||
'name': ext[1:],
|
||||
'ext': ext[1:],
|
||||
'frameStart': f"%0{len(str(frame_end))}d" % frame_start,
|
||||
'files': files,
|
||||
"stagingDir": output_dir,
|
||||
}
|
||||
|
||||
if "representations" not in instance.data:
|
||||
instance.data["representations"] = []
|
||||
instance.data["representations"].append(repre)
|
||||
|
||||
# review representation
|
||||
repre_preview = repre.copy()
|
||||
repre_preview["name"] = repre_preview["ext"] = "mp4"
|
||||
repre_preview["tags"] = ["review", "ftrackreview", "delete"]
|
||||
instance.data["representations"].append(repre_preview)
|
||||
|
||||
def render_once(self, context):
|
||||
"""Render context comp only once, even with more render instances"""
|
||||
|
||||
current_comp = context.data["currentComp"]
|
||||
frame_start = context.data["frameStartHandle"]
|
||||
frame_end = context.data["frameEndHandle"]
|
||||
|
||||
self.log.info("Starting render")
|
||||
self.log.info("Start frame: {}".format(frame_start))
|
||||
self.log.info("End frame: {}".format(frame_end))
|
||||
self.log.info(f"Start frame: {frame_start}")
|
||||
self.log.info(f"End frame: {frame_end}")
|
||||
|
||||
with comp_lock_and_undo_chunk(current_comp):
|
||||
result = current_comp.Render({
|
||||
|
|
@ -48,26 +76,5 @@ class Fusionlocal(pyblish.api.InstancePlugin):
|
|||
"Wait": True
|
||||
})
|
||||
|
||||
if "representations" not in instance.data:
|
||||
instance.data["representations"] = []
|
||||
|
||||
collected_frames = os.listdir(output_dir)
|
||||
repre = {
|
||||
'name': ext[1:],
|
||||
'ext': ext[1:],
|
||||
'frameStart': "%0{}d".format(len(str(frame_end))) % frame_start,
|
||||
'files': collected_frames,
|
||||
"stagingDir": output_dir,
|
||||
}
|
||||
instance.data["representations"].append(repre)
|
||||
|
||||
# review representation
|
||||
repre_preview = repre.copy()
|
||||
repre_preview["name"] = repre_preview["ext"] = "mp4"
|
||||
repre_preview["tags"] = ["review", "preview", "ftrackreview", "delete"]
|
||||
instance.data["representations"].append(repre_preview)
|
||||
|
||||
self.log.debug(f"_ instance.data: {pformat(instance.data)}")
|
||||
|
||||
if not result:
|
||||
raise RuntimeError("Comp render failed")
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class ValidateUniqueSubsets(pyblish.api.InstancePlugin):
|
||||
"""Ensure all instances have a unique subset name"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
label = "Validate Unique Subsets"
|
||||
families = ["render"]
|
||||
hosts = ["fusion"]
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
|
||||
context = instance.context
|
||||
subset = instance.data["subset"]
|
||||
for other_instance in context:
|
||||
if other_instance == instance:
|
||||
continue
|
||||
|
||||
if other_instance.data["subset"] == subset:
|
||||
return [instance] # current instance is invalid
|
||||
|
||||
return []
|
||||
|
||||
def process(self, instance):
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise RuntimeError("Animation content is invalid. See log.")
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
from Qt import QtWidgets
|
||||
from qtpy import QtWidgets
|
||||
import qtawesome
|
||||
from openpype.hosts.fusion.api import get_current_comp
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import json
|
|||
import signal
|
||||
import time
|
||||
from uuid import uuid4
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
from qtpy import QtWidgets, QtCore, QtGui
|
||||
import collections
|
||||
|
||||
from .server import Server
|
||||
|
|
|
|||
|
|
@ -126,10 +126,6 @@ def check_inventory():
|
|||
|
||||
def application_launch(event):
|
||||
"""Event that is executed after Harmony is launched."""
|
||||
# FIXME: This is breaking server <-> client communication.
|
||||
# It is now moved so it it manually called.
|
||||
# ensure_scene_settings()
|
||||
# check_inventory()
|
||||
# fills OPENPYPE_HARMONY_JS
|
||||
pype_harmony_path = Path(__file__).parent.parent / "js" / "PypeHarmony.js"
|
||||
pype_harmony_js = pype_harmony_path.read_text()
|
||||
|
|
@ -146,6 +142,9 @@ def application_launch(event):
|
|||
harmony.send({"script": script})
|
||||
inject_avalon_js()
|
||||
|
||||
ensure_scene_settings()
|
||||
check_inventory()
|
||||
|
||||
|
||||
def export_template(backdrops, nodes, filepath):
|
||||
"""Export Template to file.
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ class Server(threading.Thread):
|
|||
|
||||
# Create a TCP/IP socket
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
|
||||
# Bind the socket to the port
|
||||
server_address = ("127.0.0.1", port)
|
||||
|
|
@ -91,7 +92,13 @@ class Server(threading.Thread):
|
|||
self.log.info("wait ttt")
|
||||
# Receive the data in small chunks and retransmit it
|
||||
request = None
|
||||
header = self.connection.recv(10)
|
||||
try:
|
||||
header = self.connection.recv(10)
|
||||
except OSError:
|
||||
# could happen on MacOS
|
||||
self.log.info("")
|
||||
break
|
||||
|
||||
if len(header) == 0:
|
||||
# null data received, socket is closing.
|
||||
self.log.info(f"[{self.timestamp()}] Connection closing.")
|
||||
|
|
|
|||
|
|
@ -20,8 +20,9 @@ class ImageSequenceLoader(load.LoaderPlugin):
|
|||
Stores the imported asset in a container named after the asset.
|
||||
"""
|
||||
|
||||
families = ["shot", "render", "image", "plate", "reference"]
|
||||
representations = ["jpeg", "png", "jpg"]
|
||||
families = ["shot", "render", "image", "plate", "reference", "review"]
|
||||
representations = ["*"]
|
||||
extensions = {"jpeg", "png", "jpg"}
|
||||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
"""Plugin entry point.
|
||||
|
|
|
|||
|
|
@ -108,9 +108,9 @@ class ExtractRender(pyblish.api.InstancePlugin):
|
|||
output = process.communicate()[0]
|
||||
|
||||
if process.returncode != 0:
|
||||
raise ValueError(output.decode("utf-8"))
|
||||
raise ValueError(output.decode("utf-8", errors="backslashreplace"))
|
||||
|
||||
self.log.debug(output.decode("utf-8"))
|
||||
self.log.debug(output.decode("utf-8", errors="backslashreplace"))
|
||||
|
||||
# Generate representations.
|
||||
extension = collection.tail[1:]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import logging
|
||||
|
||||
from scriptsmenu import scriptsmenu
|
||||
from Qt import QtWidgets
|
||||
from qtpy import QtWidgets
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,11 @@ import secrets
|
|||
import shutil
|
||||
import hiero
|
||||
|
||||
from Qt import QtWidgets, QtCore, QtXml
|
||||
from qtpy import QtWidgets, QtCore
|
||||
try:
|
||||
from PySide import QtXml
|
||||
except ImportError:
|
||||
from PySide2 import QtXml
|
||||
|
||||
from openpype.client import get_project
|
||||
from openpype.settings import get_project_settings
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ def menu_install():
|
|||
|
||||
"""
|
||||
|
||||
from Qt import QtGui
|
||||
from qtpy import QtGui
|
||||
from . import (
|
||||
publish, launch_workfiles_app, reload_config,
|
||||
apply_colorspace_project, apply_colorspace_clips
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from copy import deepcopy
|
|||
|
||||
import hiero
|
||||
|
||||
from Qt import QtWidgets, QtCore
|
||||
from qtpy import QtWidgets, QtCore
|
||||
import qargparse
|
||||
|
||||
from openpype.settings import get_current_project_settings
|
||||
|
|
@ -276,8 +276,8 @@ class CreatorWidget(QtWidgets.QDialog):
|
|||
elif v["type"] == "QSpinBox":
|
||||
data[k]["value"] = self.create_row(
|
||||
content_layout, "QSpinBox", v["label"],
|
||||
setRange=(1, 9999999), setValue=v["value"],
|
||||
setToolTip=tool_tip)
|
||||
setValue=v["value"], setMinimum=0,
|
||||
setMaximum=100000, setToolTip=tool_tip)
|
||||
return data
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ from openpype.pipeline import (
|
|||
legacy_io,
|
||||
get_representation_path,
|
||||
)
|
||||
from openpype.lib.transcoding import (
|
||||
VIDEO_EXTENSIONS,
|
||||
IMAGE_EXTENSIONS
|
||||
)
|
||||
import openpype.hosts.hiero.api as phiero
|
||||
|
||||
|
||||
|
|
@ -17,7 +21,10 @@ class LoadClip(phiero.SequenceLoader):
|
|||
"""
|
||||
|
||||
families = ["render2d", "source", "plate", "render", "review"]
|
||||
representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264"]
|
||||
representations = ["*"]
|
||||
extensions = set(
|
||||
ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS)
|
||||
)
|
||||
|
||||
label = "Load as clip"
|
||||
order = -10
|
||||
|
|
@ -34,6 +41,38 @@ class LoadClip(phiero.SequenceLoader):
|
|||
|
||||
clip_name_template = "{asset}_{subset}_{representation}"
|
||||
|
||||
def apply_settings(cls, project_settings, system_settings):
|
||||
|
||||
plugin_type_settings = (
|
||||
project_settings
|
||||
.get("hiero", {})
|
||||
.get("load", {})
|
||||
)
|
||||
|
||||
if not plugin_type_settings:
|
||||
return
|
||||
|
||||
plugin_name = cls.__name__
|
||||
|
||||
plugin_settings = None
|
||||
# Look for plugin settings in host specific settings
|
||||
if plugin_name in plugin_type_settings:
|
||||
plugin_settings = plugin_type_settings[plugin_name]
|
||||
|
||||
if not plugin_settings:
|
||||
return
|
||||
|
||||
print(">>> We have preset for {}".format(plugin_name))
|
||||
for option, value in plugin_settings.items():
|
||||
if option == "enabled" and value is False:
|
||||
print(" - is disabled by preset")
|
||||
elif option == "representations":
|
||||
continue
|
||||
else:
|
||||
print(" - setting `{}`: `{}`".format(option, value))
|
||||
setattr(cls, option, value)
|
||||
|
||||
|
||||
def load(self, context, name, namespace, options):
|
||||
# add clip name template to options
|
||||
options.update({
|
||||
|
|
|
|||
|
|
@ -19,8 +19,9 @@ from openpype.lib import Logger
|
|||
class LoadEffects(load.LoaderPlugin):
|
||||
"""Loading colorspace soft effect exported from nukestudio"""
|
||||
|
||||
representations = ["effectJson"]
|
||||
families = ["effect"]
|
||||
representations = ["*"]
|
||||
extension = {"json"}
|
||||
|
||||
label = "Load Effects"
|
||||
order = 0
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class ExtractThumnail(publish.Extractor):
|
|||
track_item_name, thumb_frame, ".png")
|
||||
thumb_path = os.path.join(staging_dir, thumb_file)
|
||||
|
||||
thumbnail = track_item.thumbnail(thumb_frame).save(
|
||||
thumbnail = track_item.thumbnail(thumb_frame, "colour").save(
|
||||
thumb_path,
|
||||
format='png'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import tempfile
|
|||
from pprint import pformat
|
||||
|
||||
import pyblish.api
|
||||
from Qt.QtGui import QPixmap
|
||||
from qtpy.QtGui import QPixmap
|
||||
|
||||
import hiero.ui
|
||||
|
||||
|
|
|
|||
|
|
@ -144,13 +144,20 @@ class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
|||
|
||||
"""
|
||||
obj_network = hou.node("/obj")
|
||||
op_ctx = obj_network.createNode(
|
||||
"null", node_name="OpenPypeContext")
|
||||
op_ctx = obj_network.createNode("null", node_name="OpenPypeContext")
|
||||
|
||||
# A null in houdini by default comes with content inside to visualize
|
||||
# the null. However since we explicitly want to hide the node lets
|
||||
# remove the content and disable the display flag of the node
|
||||
for node in op_ctx.children():
|
||||
node.destroy()
|
||||
|
||||
op_ctx.moveToGoodPosition()
|
||||
op_ctx.setBuiltExplicitly(False)
|
||||
op_ctx.setCreatorState("OpenPype")
|
||||
op_ctx.setComment("OpenPype node to hold context metadata")
|
||||
op_ctx.setColor(hou.Color((0.081, 0.798, 0.810)))
|
||||
op_ctx.setDisplayFlag(False)
|
||||
op_ctx.hide(True)
|
||||
return op_ctx
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue