From 5d42a2ec64357e8ffbca439b50c3a59840640738 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 15 Jul 2021 12:25:56 +0200 Subject: [PATCH 01/76] add modules for ps enhancements --- .gitmodules | 8 +++++++- tools/build.ps1 | 13 +++++++++---- vendor/powershell/BurntToast | 1 + vendor/powershell/PSWriteColor | 1 + vendor/powershell/README.md | 0 5 files changed, 18 insertions(+), 5 deletions(-) create mode 160000 vendor/powershell/BurntToast create mode 160000 vendor/powershell/PSWriteColor create mode 100644 vendor/powershell/README.md diff --git a/.gitmodules b/.gitmodules index 52f2fc0750..6c12c76b16 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,4 +9,10 @@ url = https://bitbucket.org/ftrack/ftrack-python-api.git [submodule "openpype/modules/ftrack/python2_vendor/arrow"] path = openpype/modules/ftrack/python2_vendor/arrow - url = https://github.com/arrow-py/arrow.git \ No newline at end of file + url = https://github.com/arrow-py/arrow.git +[submodule "vendor/powershell/BurntToast"] + path = vendor/powershell/BurntToast + url = https://github.com/Windos/BurntToast.git +[submodule "vendor/powershell/PSWriteColor"] + path = vendor/powershell/PSWriteColor + url = "https://github.com/EvotecIT/PSWriteColor.git" \ No newline at end of file diff --git a/tools/build.ps1 b/tools/build.ps1 index cc4253fe24..00cec54927 100644 --- a/tools/build.ps1 +++ b/tools/build.ps1 @@ -28,6 +28,13 @@ if($arguments -eq "--no-submodule-update") { $disable_submodule_update=$true } +$current_dir = Get-Location +$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent +$openpype_root = (Get-Item $script_dir).parent.FullName + +# Install PSWriteColor to support colorized output to terminal +$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell" + function Start-Progress { param([ScriptBlock]$code) $scroll = "/-\|/-\|" @@ -110,10 +117,6 @@ Write-Host $art -ForegroundColor DarkGreen # Enable if PS 7.x is needed. # Show-PSWarning -$current_dir = Get-Location -$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent -$openpype_root = (Get-Item $script_dir).parent.FullName - $env:_INSIDE_OPENPYPE_TOOL = "1" if (-not (Test-Path 'env:POETRY_HOME')) { @@ -201,6 +204,8 @@ Write-Host "restoring current directory" Set-Location -Path $current_dir $endTime = [int][double]::Parse((Get-Date -UFormat %s)) +New-BurntToastNotification -AppLogo "$openpype_root/openpype/resources/icons/openpype_icon.png" -Text "OpenPype build complete!", "All done in $($endTime - $startTime) secs. You will find OpenPype and build log in build directory." + Write-Host "*** " -NoNewline -ForegroundColor Cyan Write-Host "All done in $($endTime - $startTime) secs. You will find OpenPype and build log in " -NoNewLine Write-Host "'.\build'" -NoNewline -ForegroundColor Green diff --git a/vendor/powershell/BurntToast b/vendor/powershell/BurntToast new file mode 160000 index 0000000000..ae0acdd870 --- /dev/null +++ b/vendor/powershell/BurntToast @@ -0,0 +1 @@ +Subproject commit ae0acdd870a2fd8d9f0d147de22dc36d6c5e399e diff --git a/vendor/powershell/PSWriteColor b/vendor/powershell/PSWriteColor new file mode 160000 index 0000000000..12eda384eb --- /dev/null +++ b/vendor/powershell/PSWriteColor @@ -0,0 +1 @@ +Subproject commit 12eda384ebd7a7954e15855e312215c009c97114 diff --git a/vendor/powershell/README.md b/vendor/powershell/README.md new file mode 100644 index 0000000000..e69de29bb2 From 0e6f4e330daa343b16a4b9bb1f6427eebf7706c0 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 16 Jul 2021 17:53:44 +0200 Subject: [PATCH 02/76] PS enhancement I. --- tools/build.ps1 | 63 ++++++++++-------------------- tools/build_win_installer.ps1 | 50 ++++++++++++------------ tools/create_env.ps1 | 72 ++++++++++++++++------------------- 3 files changed, 79 insertions(+), 106 deletions(-) diff --git a/tools/build.ps1 b/tools/build.ps1 index 00cec54927..89d2eac3a4 100644 --- a/tools/build.ps1 +++ b/tools/build.ps1 @@ -79,17 +79,14 @@ function Exit-WithCode($exitcode) { function Show-PSWarning() { if ($PSVersionTable.PSVersion.Major -lt 7) { - Write-Host "!!! " -NoNewline -ForegroundColor Red - Write-Host "You are using old version of PowerShell. $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" - Write-Host "Please update to at least 7.0 - " -NoNewline -ForegroundColor Gray - Write-Host "https://github.com/PowerShell/PowerShell/releases" -ForegroundColor White + Write-Color -Text "!!! ", "You are using old version of PowerShell - ", "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" -Color Red, Yellow, White + Write-Color -Text " Please update to at least 7.0 - ", "https://github.com/PowerShell/PowerShell/releases" -Color Yellow, White Exit-WithCode 1 } } function Install-Poetry() { - Write-Host ">>> " -NoNewline -ForegroundColor Green - Write-Host "Installing Poetry ... " + Write-Color -Text ">>> ", "Installing Poetry ... " -Color Green, Gray $env:POETRY_HOME="$openpype_root\.poetry" (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | python - } @@ -129,8 +126,7 @@ $version_file = Get-Content -Path "$($openpype_root)\openpype\version.py" $result = [regex]::Matches($version_file, '__version__ = "(?\d+\.\d+.\d+.*)"') $openpype_version = $result[0].Groups['version'].Value if (-not $openpype_version) { - Write-Host "!!! " -ForegroundColor yellow -NoNewline - Write-Host "Cannot determine OpenPype version." + Write-Color -Text "!!! ", "Cannot determine OpenPype version." -Color Yellow, Gray Exit-WithCode 1 } @@ -139,74 +135,57 @@ if (-not (Test-Path -PathType Container -Path "$($openpype_root)\build")) { New-Item -ItemType Directory -Force -Path "$($openpype_root)\build" } -Write-Host "--- " -NoNewline -ForegroundColor yellow -Write-Host "Cleaning build directory ..." +Write-Color -Text "--- ", "Cleaning build directory ..." -Color Yellow, Gray try { Remove-Item -Recurse -Force "$($openpype_root)\build\*" } catch { - Write-Host "!!! " -NoNewline -ForegroundColor Red - Write-Host "Cannot clean build directory, possibly because process is using it." - Write-Host $_.Exception.Message + Write-Color -Text "!!! ", "Cannot clean build directory, possibly because process is using it." -Color Red, Gray + Write-Color -Text $_.Exception.Message -Color Red Exit-WithCode 1 } if (-not $disable_submodule_update) { - Write-Host ">>> " -NoNewLine -ForegroundColor green - Write-Host "Making sure submodules are up-to-date ..." - git submodule update --init --recursive + Write-Color -Text ">>> ", "Making sure submodules are up-to-date ..." -Color Green, Gray + & git submodule update --init --recursive } else { - Write-Host "*** " -NoNewLine -ForegroundColor yellow - Write-Host "Not updating submodules ..." + Write-Color -Text "*** ", "Not updating submodules ..." -Color Green, Gray } -Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "OpenPype [ " -NoNewline -ForegroundColor white -Write-host $openpype_version -NoNewline -ForegroundColor green -Write-Host " ]" -ForegroundColor white +Write-Color -Text ">>> ", "OpenPype [ ", $openpype_version, " ]" -Color Green, White, Cyan, White -Write-Host ">>> " -NoNewline -ForegroundColor Green -Write-Host "Reading Poetry ... " -NoNewline +Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { - Write-Host "NOT FOUND" -ForegroundColor Yellow - Write-Host "*** " -NoNewline -ForegroundColor Yellow - Write-Host "We need to install Poetry create virtual env first ..." + Write-Color -Text "NOT FOUND" -Color Yellow + Write-Color -Text "*** ", "We need to install Poetry create virtual env first ..." -Color Yellow, Gray & "$openpype_root\tools\create_env.ps1" } else { - Write-Host "OK" -ForegroundColor Green + Write-Color -Text "OK" -Color Green } -Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "Cleaning cache files ... " -NoNewline +Write-Color -Text ">>> ", "Cleaning cache files ... " -Color Green, Gray -NoNewline Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force Get-ChildItem $openpype_root -Filter "*.pyo" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force Get-ChildItem $openpype_root -Filter "__pycache__" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force -Recurse -Write-Host "OK" -ForegroundColor green +Write-Color -Text "OK" -Color green -Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "Building OpenPype ..." +Write-Color -Text ">>> ", "Building OpenPype ..." -Color Green, White $startTime = [int][double]::Parse((Get-Date -UFormat %s)) $out = & "$($env:POETRY_HOME)\bin\poetry" run python setup.py build 2>&1 Set-Content -Path "$($openpype_root)\build\build.log" -Value $out if ($LASTEXITCODE -ne 0) { - Write-Host "!!! " -NoNewLine -ForegroundColor Red - Write-Host "Build failed. Check the log: " -NoNewline - Write-Host ".\build\build.log" -ForegroundColor Yellow + Write-Color -Text "!!! ", "Build failed. Check the log: ", ".\build\build.log" -Color Red, Yellow, White Exit-WithCode $LASTEXITCODE } Set-Content -Path "$($openpype_root)\build\build.log" -Value $out & "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\tools\build_dependencies.py" -Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "restoring current directory" +Write-Color -Text ">>> ", "Restoring current directory" -Color Green, Gray Set-Location -Path $current_dir $endTime = [int][double]::Parse((Get-Date -UFormat %s)) New-BurntToastNotification -AppLogo "$openpype_root/openpype/resources/icons/openpype_icon.png" -Text "OpenPype build complete!", "All done in $($endTime - $startTime) secs. You will find OpenPype and build log in build directory." -Write-Host "*** " -NoNewline -ForegroundColor Cyan -Write-Host "All done in $($endTime - $startTime) secs. You will find OpenPype and build log in " -NoNewLine -Write-Host "'.\build'" -NoNewline -ForegroundColor Green -Write-Host " directory." +Write-Color -Text "*** ", "All done in ", $($endTime - $startTime), " secs. You will find OpenPype and build log in ", "'.\build'", " directory." -Color Green, Gray, White, Gray, White, Gray diff --git a/tools/build_win_installer.ps1 b/tools/build_win_installer.ps1 index a0832e0135..287e5c751f 100644 --- a/tools/build_win_installer.ps1 +++ b/tools/build_win_installer.ps1 @@ -11,6 +11,12 @@ PS> .\build_win_installer.ps1 #> +$current_dir = Get-Location +$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent +$openpype_root = (Get-Item $script_dir).parent.FullName + +# Install PSWriteColor to support colorized output to terminal +$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell" function Start-Progress { param([ScriptBlock]$code) @@ -44,7 +50,6 @@ function Start-Progress { #> } - function Exit-WithCode($exitcode) { # Only exit this host process if it's a child of another PowerShell parent process... $parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId @@ -56,10 +61,8 @@ function Exit-WithCode($exitcode) { function Show-PSWarning() { if ($PSVersionTable.PSVersion.Major -lt 7) { - Write-Host "!!! " -NoNewline -ForegroundColor Red - Write-Host "You are using old version of PowerShell. $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" - Write-Host "Please update to at least 7.0 - " -NoNewline -ForegroundColor Gray - Write-Host "https://github.com/PowerShell/PowerShell/releases" -ForegroundColor White + Write-Color -Text "!!! ", "You are using old version of PowerShell - ", "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" -Color Red, Yellow, White + Write-Color -Text " Please update to at least 7.0 - ", "https://github.com/PowerShell/PowerShell/releases" -Color Yellow, White Exit-WithCode 1 } } @@ -87,9 +90,6 @@ Write-Host $art -ForegroundColor DarkGreen # Enable if PS 7.x is needed. # Show-PSWarning -$current_dir = Get-Location -$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent -$openpype_root = (Get-Item $script_dir).parent.FullName Set-Location -Path $openpype_root @@ -97,16 +97,15 @@ $version_file = Get-Content -Path "$($openpype_root)\openpype\version.py" $result = [regex]::Matches($version_file, '__version__ = "(?\d+\.\d+.\d+.*)"') $openpype_version = $result[0].Groups['version'].Value if (-not $openpype_version) { - Write-Host "!!! " -ForegroundColor yellow -NoNewline - Write-Host "Cannot determine OpenPype version." + Write-Color -Text "!!! ", "Cannot determine OpenPype version." -Color Yellow, Gray Exit-WithCode 1 } + $env:BUILD_VERSION = $openpype_version iscc -Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "Creating OpenPype installer ... " -ForegroundColor white +Write-Color -Text ">>> ", "Creating OpenPype installer ... " -Color Green, White $build_dir_command = @" import sys @@ -115,24 +114,25 @@ print('exe.{}-{}'.format(get_platform(), sys.version[0:3])) "@ $build_dir = & python -c $build_dir_command -Write-Host "Build directory ... ${build_dir}" -ForegroundColor white +Write-Color -Text "--- ", "Build directory ", "${build_dir}" -Color Green, Gray, White $env:BUILD_DIR = $build_dir -if (Get-Command iscc -errorAction SilentlyContinue -ErrorVariable ProcessError) -{ - iscc "$openpype_root\inno_setup.iss" -}else { - Write-Host "!!! Cannot find Inno Setup command" -ForegroundColor red - Write-Host "!!! You can download it at https://jrsoftware.org/" -ForegroundColor red +if (-not (Get-Command iscc -errorAction SilentlyContinue -ErrorVariable ProcessError)) { + Write-Color -Text "!!! ", "Cannot find Inno Setup command" -Color Red, Yellow + Write-Color "!!! You can download it at https://jrsoftware.org/" -ForegroundColor red Exit-WithCode 1 } +& iscc "$openpype_root\inno_setup.iss" -Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "restoring current directory" +if ($LASTEXITCODE -ne 0) { + Write-Color -Text "!!! ", "Creating installer failed." -Color Red, Yellow + Exit-WithCode 1 +} + +Write-Color -Text ">>> ", "Restoring current directory" -Color Green, Gray Set-Location -Path $current_dir -Write-Host "*** " -NoNewline -ForegroundColor Cyan -Write-Host "All done. You will find OpenPype installer in " -NoNewLine -Write-Host "'.\build'" -NoNewline -ForegroundColor Green -Write-Host " directory." +New-BurntToastNotification -AppLogo "$openpype_root/openpype/resources/icons/openpype_icon.png" -Text "OpenPype build complete!", "All done. You will find You will find OpenPype installer in '.\build' directory." + +Write-Color -Text "*** ", "All done. You will find OpenPype installer in ", "'.\build'", " directory." -Color Green, Gray, White, Gray diff --git a/tools/create_env.ps1 b/tools/create_env.ps1 index 6c8124ccb2..6315b7b27c 100644 --- a/tools/create_env.ps1 +++ b/tools/create_env.ps1 @@ -24,6 +24,13 @@ if($arguments -eq "--verbose") { $poetry_verbosity="-vvv" } +$current_dir = Get-Location +$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent +$openpype_root = (Get-Item $script_dir).parent.FullName + +# Install PSWriteColor to support colorized output to terminal +$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell" + function Exit-WithCode($exitcode) { # Only exit this host process if it's a child of another PowerShell parent process... $parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId @@ -36,28 +43,24 @@ function Exit-WithCode($exitcode) { function Show-PSWarning() { if ($PSVersionTable.PSVersion.Major -lt 7) { - Write-Host "!!! " -NoNewline -ForegroundColor Red - Write-Host "You are using old version of PowerShell. $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" - Write-Host "Please update to at least 7.0 - " -NoNewline -ForegroundColor Gray - Write-Host "https://github.com/PowerShell/PowerShell/releases" -ForegroundColor White + Write-Color -Text "!!! ", "You are using old version of PowerShell - ", "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" -Color Red, Yellow, White + Write-Color -Text " Please update to at least 7.0 - ", "https://github.com/PowerShell/PowerShell/releases" -Color Yellow, White Exit-WithCode 1 } } function Install-Poetry() { - Write-Host ">>> " -NoNewline -ForegroundColor Green - Write-Host "Installing Poetry ... " + Write-Color -Text ">>> ", "Installing Poetry ... " -Color Green, Gray $env:POETRY_HOME="$openpype_root\.poetry" (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | python - } function Test-Python() { - Write-Host ">>> " -NoNewline -ForegroundColor green - Write-Host "Detecting host Python ... " -NoNewline + Write-Color -Text ">>> ", "Detecting host Python ... " -Color Green, Gray -NoNewline if (-not (Get-Command "python" -ErrorAction SilentlyContinue)) { - Write-Host "!!! Python not detected" -ForegroundColor red + Write-Color -Text "!!! ", "Python not detected" -Color Red, Yellow Set-Location -Path $current_dir Exit-WithCode 1 } @@ -70,28 +73,24 @@ print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1])) $env:PYTHON_VERSION = $p $m = $p -match '(\d+)\.(\d+)' if(-not $m) { - Write-Host "!!! Cannot determine version" -ForegroundColor red - Set-Location -Path $current_dir - Exit-WithCode 1 + Write-Color -Text "FAILED " -Color Red + Write-Color -Text "!!! ", "Cannot determine version" -Color Red, Yellow + Set-Location -Path $current_dir + Exit-WithCode 1 } # We are supporting python 3.7 only if (($matches[1] -lt 3) -or ($matches[2] -lt 7)) { - Write-Host "FAILED Version [ $p ] is old and unsupported" -ForegroundColor red + Write-Color -Text "FAILED ", "Version ", "[", $p ,"]", "is old and unsupported" -Color Red, Yellow, Cyan, White, Cyan, Yellow Set-Location -Path $current_dir Exit-WithCode 1 } elseif (($matches[1] -eq 3) -and ($matches[2] -gt 7)) { - Write-Host "WARNING Version [ $p ] is unsupported, use at your own risk." -ForegroundColor yellow - Write-Host "*** " -NoNewline -ForegroundColor yellow - Write-Host "OpenPype supports only Python 3.7" -ForegroundColor white + Write-Color -Text "WARNING Version ", "[", $p, "]", " is unsupported, use at your own risk." -Color Yellow, Cyan, White, Cyan, Yellow + Write-Color -Text "*** ", "OpenPype supports only Python 3.7" -Color Yellow, White } else { - Write-Host "OK [ $p ]" -ForegroundColor green + Write-Color "OK ", "[", $p, "]" -Color Green, Cyan, White, Cyan } } -$current_dir = Get-Location -$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent -$openpype_root = (Get-Item $script_dir).parent.FullName - if (-not (Test-Path 'env:POETRY_HOME')) { $env:POETRY_HOME = "$openpype_root\.poetry" } @@ -129,41 +128,36 @@ $version_file = Get-Content -Path "$($openpype_root)\openpype\version.py" $result = [regex]::Matches($version_file, '__version__ = "(?\d+\.\d+.\d+.*)"') $openpype_version = $result[0].Groups['version'].Value if (-not $openpype_version) { - Write-Host "!!! " -ForegroundColor yellow -NoNewline - Write-Host "Cannot determine OpenPype version." + Write-Color -Text "!!! ", "Cannot determine OpenPype version." -Color Red, Yellow Set-Location -Path $current_dir Exit-WithCode 1 } -Write-Host ">>> " -NoNewline -ForegroundColor Green -Write-Host "Found OpenPype version " -NoNewline -Write-Host "[ $($openpype_version) ]" -ForegroundColor Green +Write-Color -Text ">>> ", "Found OpenPype version ", "[ ", $($openpype_version), " ]" -Color Green, Gray, Cyan, White, Cyan Test-Python -Write-Host ">>> " -NoNewline -ForegroundColor Green -Write-Host "Reading Poetry ... " -NoNewline +Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { - Write-Host "NOT FOUND" -ForegroundColor Yellow + Write-Color -Text "NOT FOUND" -Color Yellow Install-Poetry - Write-Host "INSTALLED" -ForegroundColor Cyan + Write-Color -Text "INSTALLED" -Color Cyan } else { - Write-Host "OK" -ForegroundColor Green + Write-Color -Text "OK" -Color Green } if (-not (Test-Path -PathType Leaf -Path "$($openpype_root)\poetry.lock")) { - Write-Host ">>> " -NoNewline -ForegroundColor green - Write-Host "Installing virtual environment and creating lock." + Write-Color -Text ">>> ", "Installing virtual environment and creating lock." -Color Green, Gray } else { - Write-Host ">>> " -NoNewline -ForegroundColor green - Write-Host "Installing virtual environment from lock." + Write-Color -Text ">>> ", "Installing virtual environment from lock." -Color Green, Gray } & "$env:POETRY_HOME\bin\poetry" install --no-root $poetry_verbosity --ansi if ($LASTEXITCODE -ne 0) { - Write-Host "!!! " -ForegroundColor yellow -NoNewline - Write-Host "Poetry command failed." + Write-Color -Text "!!! ", "Poetry command failed." -Color Red, Yellow Set-Location -Path $current_dir Exit-WithCode 1 } Set-Location -Path $current_dir -Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "Virtual environment created." + +New-BurntToastNotification -AppLogo "$openpype_root/openpype/resources/icons/openpype_icon.png" -Text "OpenPype", "Virtual environment created." + +Write-Color -Text ">>> ", "Virtual environment created." -Color Green, White From e7e9f461eeed634f0cba4b931fe8ea33814f26b4 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 2 Sep 2021 14:55:04 +0200 Subject: [PATCH 03/76] remove submodules --- openpype/modules/ftrack/python2_vendor/arrow | 1 - openpype/modules/ftrack/python2_vendor/ftrack-python-api | 1 - 2 files changed, 2 deletions(-) delete mode 160000 openpype/modules/ftrack/python2_vendor/arrow delete mode 160000 openpype/modules/ftrack/python2_vendor/ftrack-python-api diff --git a/openpype/modules/ftrack/python2_vendor/arrow b/openpype/modules/ftrack/python2_vendor/arrow deleted file mode 160000 index b746fedf72..0000000000 --- a/openpype/modules/ftrack/python2_vendor/arrow +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b746fedf7286c3755a46f07ab72f4c414cd41fc0 diff --git a/openpype/modules/ftrack/python2_vendor/ftrack-python-api b/openpype/modules/ftrack/python2_vendor/ftrack-python-api deleted file mode 160000 index d277f474ab..0000000000 --- a/openpype/modules/ftrack/python2_vendor/ftrack-python-api +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d277f474ab016e7b53479c36af87cb861d0cc53e From 757af0d36eee3a35d0f2de5ce131fc4e3a9327c5 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 2 Sep 2021 23:31:34 +0200 Subject: [PATCH 04/76] yet more scripts --- tools/run_mongo.ps1 | 22 +++++++-------- tools/run_project_manager.ps1 | 15 +++++----- tools/run_settings.ps1 | 15 +++++----- tools/run_tests.ps1 | 53 +++++++++++++---------------------- tools/run_tray.ps1 | 13 +++++---- 5 files changed, 54 insertions(+), 64 deletions(-) diff --git a/tools/run_mongo.ps1 b/tools/run_mongo.ps1 index 32f6cfed17..0f26e86579 100644 --- a/tools/run_mongo.ps1 +++ b/tools/run_mongo.ps1 @@ -11,6 +11,13 @@ PS> .\run_mongo.ps1 #> +$current_dir = Get-Location +$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent +$openpype_root = (Get-Item $script_dir).parent.FullName + +# Install PSWriteColor to support colorized output to terminal +$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell" + $art = @" . . .. . .. @@ -43,8 +50,7 @@ function Exit-WithCode($exitcode) { function Find-Mongo ($preferred_version) { $defaultPath = "C:\Program Files\MongoDB\Server" - Write-Host ">>> " -NoNewLine -ForegroundColor Green - Write-Host "Detecting MongoDB ... " -NoNewline + Write-Color -Text ">>> ", "Detecting MongoDB ... " -Color Geen, Gray -NoNewline if (-not (Get-Command "mongod" -ErrorAction SilentlyContinue)) { if(Test-Path "$($defaultPath)\*\bin\mongod.exe" -PathType Leaf) { # we have mongo server installed on standard Windows location @@ -52,17 +58,14 @@ function Find-Mongo ($preferred_version) { # $preferred_version. $mongoVersions = Get-ChildItem -Directory 'C:\Program Files\MongoDB\Server' | Sort-Object -Property {$_.Name -as [int]} if(Test-Path "$($mongoVersions[-1])\bin\mongod.exe" -PathType Leaf) { - Write-Host "OK" -ForegroundColor Green + Write-Color -Text "OK" -Color Green $use_version = $mongoVersions[-1] foreach ($v in $mongoVersions) { - Write-Host " - found [ " -NoNewline - Write-Host $v -NoNewLine -ForegroundColor Cyan - Write-Host " ]" -NoNewLine - + Write-Color -Text " - found [ ", $v, " ]" - Color Cyan, White, Cyan -NoNewLine $version = Split-Path $v -Leaf if ($preferred_version -eq $version) { - Write-Host " *" -ForegroundColor Green + Write-Color -Text " *" -Color Green $use_version = $v } else { Write-Host "" @@ -104,9 +107,6 @@ function Find-Mongo ($preferred_version) { #> } -$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent -$openpype_root = (Get-Item $script_dir).parent.FullName - # mongodb port $port = 2707 diff --git a/tools/run_project_manager.ps1 b/tools/run_project_manager.ps1 index a9cfbb1e7b..2932358c2a 100644 --- a/tools/run_project_manager.ps1 +++ b/tools/run_project_manager.ps1 @@ -35,6 +35,9 @@ $current_dir = Get-Location $script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent $openpype_root = (Get-Item $script_dir).parent.FullName +# Install PSWriteColor to support colorized output to terminal +$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell" + $env:_INSIDE_OPENPYPE_TOOL = "1" # make sure Poetry is in PATH @@ -45,15 +48,13 @@ $env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" Set-Location -Path $openpype_root -Write-Host ">>> " -NoNewline -ForegroundColor Green -Write-Host "Reading Poetry ... " -NoNewline +Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { - Write-Host "NOT FOUND" -ForegroundColor Yellow - Write-Host "*** " -NoNewline -ForegroundColor Yellow - Write-Host "We need to install Poetry create virtual env first ..." - & "$openpype_root\tools\create_env.ps1" + Write-Color -Text "NOT FOUND" -Color Yellow + Install-Poetry + Write-Color -Text "INSTALLED" -Color Cyan } else { - Write-Host "OK" -ForegroundColor Green + Write-Color -Text "OK" -Color Green } & "$env:POETRY_HOME\bin\poetry" run python "$($openpype_root)\start.py" projectmanager diff --git a/tools/run_settings.ps1 b/tools/run_settings.ps1 index 1c0aa6e8f3..918ea367ab 100644 --- a/tools/run_settings.ps1 +++ b/tools/run_settings.ps1 @@ -15,6 +15,9 @@ $current_dir = Get-Location $script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent $openpype_root = (Get-Item $script_dir).parent.FullName +# Install PSWriteColor to support colorized output to terminal +$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell" + $env:_INSIDE_OPENPYPE_TOOL = "1" # make sure Poetry is in PATH @@ -25,15 +28,13 @@ $env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" Set-Location -Path $openpype_root -Write-Host ">>> " -NoNewline -ForegroundColor Green -Write-Host "Reading Poetry ... " -NoNewline +Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { - Write-Host "NOT FOUND" -ForegroundColor Yellow - Write-Host "*** " -NoNewline -ForegroundColor Yellow - Write-Host "We need to install Poetry create virtual env first ..." - & "$openpype_root\tools\create_env.ps1" + Write-Color -Text "NOT FOUND" -Color Yellow + Install-Poetry + Write-Color -Text "INSTALLED" -Color Cyan } else { - Write-Host "OK" -ForegroundColor Green + Write-Color -Text "OK" -Color Green } & "$env:POETRY_HOME\bin\poetry" run python "$($openpype_root)\start.py" settings --dev diff --git a/tools/run_tests.ps1 b/tools/run_tests.ps1 index e631cb72df..7995c6a8e9 100644 --- a/tools/run_tests.ps1 +++ b/tools/run_tests.ps1 @@ -11,6 +11,13 @@ PS> .\run_test.ps1 #> +$current_dir = Get-Location +$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent +$openpype_root = (Get-Item $script_dir).parent.FullName + +# Install PSWriteColor to support colorized output to terminal +$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell" + function Exit-WithCode($exitcode) { # Only exit this host process if it's a child of another PowerShell parent process... $parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId @@ -22,10 +29,8 @@ function Exit-WithCode($exitcode) { function Show-PSWarning() { if ($PSVersionTable.PSVersion.Major -lt 7) { - Write-Host "!!! " -NoNewline -ForegroundColor Red - Write-Host "You are using old version of PowerShell. $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" - Write-Host "Please update to at least 7.0 - " -NoNewline -ForegroundColor Gray - Write-Host "https://github.com/PowerShell/PowerShell/releases" -ForegroundColor White + Write-Color -Text "!!! ", "You are using old version of PowerShell - ", "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" -Color Red, Yellow, White + Write-Color -Text " Please update to at least 7.0 - ", "https://github.com/PowerShell/PowerShell/releases" -Color Yellow, White Exit-WithCode 1 } } @@ -53,10 +58,6 @@ Write-Host $art -ForegroundColor DarkGreen # Enable if PS 7.x is needed. # Show-PSWarning -$current_dir = Get-Location -$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent -$openpype_root = (Get-Item $script_dir).parent.FullName - $env:_INSIDE_OPENPYPE_TOOL = "1" if (-not (Test-Path 'env:POETRY_HOME')) { @@ -69,46 +70,32 @@ $version_file = Get-Content -Path "$($openpype_root)\openpype\version.py" $result = [regex]::Matches($version_file, '__version__ = "(?\d+\.\d+.\d+.*)"') $openpype_version = $result[0].Groups['version'].Value if (-not $openpype_version) { - Write-Host "!!! " -ForegroundColor yellow -NoNewline - Write-Host "Cannot determine OpenPype version." + Write-Color -Text "!!! ", "Cannot determine OpenPype version." -Color Yellow, Gray Exit-WithCode 1 } -Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "OpenPype [ " -NoNewline -ForegroundColor white -Write-host $openpype_version -NoNewline -ForegroundColor green -Write-Host " ] ..." -ForegroundColor white +Write-Color -Text ">>> ", "OpenPype [ ", $openpype_version, " ]" -Color Green, White, Cyan, White -Write-Host ">>> " -NoNewline -ForegroundColor Green -Write-Host "Reading Poetry ... " -NoNewline +Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { - Write-Host "NOT FOUND" -ForegroundColor Yellow - Write-Host "*** " -NoNewline -ForegroundColor Yellow - Write-Host "We need to install Poetry create virtual env first ..." + Write-Color -Text "NOT FOUND" -Color Yellow + Write-Color -Text "*** ", "We need to install Poetry create virtual env first ..." -Color Yellow, Gray & "$openpype_root\tools\create_env.ps1" } else { - Write-Host "OK" -ForegroundColor Green + Write-Color -Text "OK" -Color Green } -Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "Cleaning cache files ... " -NoNewline +Write-Color -Text ">>> ", "Cleaning cache files ... " -Color Green, Gray -NoNewline Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force +Get-ChildItem $openpype_root -Filter "*.pyo" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force Get-ChildItem $openpype_root -Filter "__pycache__" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force -Recurse -Write-Host "OK" -ForegroundColor green +Write-Color -Text "OK" -Color green -Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "Testing OpenPype ..." +Write-Color -Text ">>> ", "Testing OpenPype ..." -Color Green, White $original_pythonpath = $env:PYTHONPATH $env:PYTHONPATH="$($openpype_root);$($env:PYTHONPATH)" & "$env:POETRY_HOME\bin\poetry" run pytest -x --capture=sys --print -W ignore::DeprecationWarning "$($openpype_root)/tests" $env:PYTHONPATH = $original_pythonpath -Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "restoring current directory" +Write-Color -Text ">>> ", "Restoring current directory" -Color Green, Gray Set-Location -Path $current_dir - - - - - - diff --git a/tools/run_tray.ps1 b/tools/run_tray.ps1 index 872c1524a6..7dee3d0064 100644 --- a/tools/run_tray.ps1 +++ b/tools/run_tray.ps1 @@ -14,6 +14,9 @@ $current_dir = Get-Location $script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent $openpype_root = (Get-Item $script_dir).parent.FullName +# Install PSWriteColor to support colorized output to terminal +$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell" + $env:_INSIDE_OPENPYPE_TOOL = "1" # make sure Poetry is in PATH @@ -24,15 +27,13 @@ $env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" Set-Location -Path $openpype_root -Write-Host ">>> " -NoNewline -ForegroundColor Green -Write-Host "Reading Poetry ... " -NoNewline +Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { - Write-Host "NOT FOUND" -ForegroundColor Yellow - Write-Host "*** " -NoNewline -ForegroundColor Yellow - Write-Host "We need to install Poetry create virtual env first ..." + Write-Color -Text "NOT FOUND" -Color Yellow + Write-Color -Text "*** ", "We need to install Poetry create virtual env first ..." -Color Yellow, Gray & "$openpype_root\tools\create_env.ps1" } else { - Write-Host "OK" -ForegroundColor Green + Write-Color -Text "OK" -Color Green } & "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" tray --debug From cd468f567331cc8125318b92a62259cf621e48db Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 3 Sep 2021 12:10:55 +0200 Subject: [PATCH 05/76] rest of the scripts converted --- tools/create_zip.ps1 | 40 +++++++++++++++------------------ tools/fetch_thirdparty_libs.ps1 | 13 ++++++----- tools/make_docs.ps1 | 27 ++++++++++++---------- tools/run_mongo.ps1 | 33 ++++++++------------------- 4 files changed, 49 insertions(+), 64 deletions(-) diff --git a/tools/create_zip.ps1 b/tools/create_zip.ps1 index c27857b480..3796186dd0 100644 --- a/tools/create_zip.ps1 +++ b/tools/create_zip.ps1 @@ -19,6 +19,13 @@ PS> .\create_zip.ps1 --path C:\OpenPype #> +$current_dir = Get-Location +$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent +$openpype_root = (Get-Item $script_dir).parent.FullName + +# Install PSWriteColor to support colorized output to terminal +$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell" + function Exit-WithCode($exitcode) { # Only exit this host process if it's a child of another PowerShell parent process... $parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId @@ -31,18 +38,12 @@ function Exit-WithCode($exitcode) { function Show-PSWarning() { if ($PSVersionTable.PSVersion.Major -lt 7) { - Write-Host "!!! " -NoNewline -ForegroundColor Red - Write-Host "You are using old version of PowerShell. $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" - Write-Host "Please update to at least 7.0 - " -NoNewline -ForegroundColor Gray - Write-Host "https://github.com/PowerShell/PowerShell/releases" -ForegroundColor White + Write-Color -Text "!!! ", "You are using old version of PowerShell - ", "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" -Color Red, Yellow, White + Write-Color -Text " Please update to at least 7.0 - ", "https://github.com/PowerShell/PowerShell/releases" -Color Yellow, White Exit-WithCode 1 } } -$current_dir = Get-Location -$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent -$openpype_root = (Get-Item $script_dir).parent.FullName - $env:_INSIDE_OPENPYPE_TOOL = "1" if (-not (Test-Path 'env:POETRY_HOME')) { @@ -78,31 +79,26 @@ $version_file = Get-Content -Path "$($openpype_root)\openpype\version.py" $result = [regex]::Matches($version_file, '__version__ = "(?\d+\.\d+.\d+.*)"') $openpype_version = $result[0].Groups['version'].Value if (-not $openpype_version) { - Write-Host "!!! " -ForegroundColor yellow -NoNewline - Write-Host "Cannot determine OpenPype version." + Write-Color -Text "!!! ", "Cannot determine OpenPype version." -Color Yellow, Gray Exit-WithCode 1 } -Write-Host ">>> " -NoNewline -ForegroundColor Green -Write-Host "Reading Poetry ... " -NoNewline +Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { - Write-Host "NOT FOUND" -ForegroundColor Yellow - Write-Host "*** " -NoNewline -ForegroundColor Yellow - Write-Host "We need to install Poetry create virtual env first ..." + Write-Color -Text "NOT FOUND" -Color Yellow + Write-Color -Text "*** ", "We need to install Poetry create virtual env first ..." -Color Yellow, Gray & "$openpype_root\tools\create_env.ps1" } else { - Write-Host "OK" -ForegroundColor Green + Write-Color -Text "OK" -Color Green } -Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "Cleaning cache files ... " -NoNewline +Write-Color -Text ">>> ", "Cleaning cache files ... " -Color Green, Gray -NoNewline Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force Get-ChildItem $openpype_root -Filter "*.pyo" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force -Get-ChildItem $openpype_root -Filter "__pycache__" -Force -Recurse| Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force -Recurse -Write-Host "OK" -ForegroundColor green +Get-ChildItem $openpype_root -Filter "__pycache__" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force -Recurse +Write-Color -Text "OK" -Color green -Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "Generating zip from current sources ..." +Write-Color -Text ">>> ", "Generating zip from current sources ..." -Color Green, Gray $env:PYTHONPATH="$($openpype_root);$($env:PYTHONPATH)" $env:OPENPYPE_ROOT="$($openpype_root)" & "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\tools\create_zip.py" $ARGS diff --git a/tools/fetch_thirdparty_libs.ps1 b/tools/fetch_thirdparty_libs.ps1 index 16f7b70e7a..0226a35bfb 100644 --- a/tools/fetch_thirdparty_libs.ps1 +++ b/tools/fetch_thirdparty_libs.ps1 @@ -15,6 +15,9 @@ $current_dir = Get-Location $script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent $openpype_root = (Get-Item $script_dir).parent.FullName +# Install PSWriteColor to support colorized output to terminal +$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell" + $env:_INSIDE_OPENPYPE_TOOL = "1" if (-not (Test-Path 'env:POETRY_HOME')) { @@ -23,15 +26,13 @@ if (-not (Test-Path 'env:POETRY_HOME')) { Set-Location -Path $openpype_root -Write-Host ">>> " -NoNewline -ForegroundColor Green -Write-Host "Reading Poetry ... " -NoNewline +Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { - Write-Host "NOT FOUND" -ForegroundColor Yellow - Write-Host "*** " -NoNewline -ForegroundColor Yellow - Write-Host "We need to install Poetry create virtual env first ..." + Write-Color -Text "NOT FOUND" -Color Yellow + Write-Color -Text "*** ", "We need to install Poetry create virtual env first ..." -Color Yellow, Gray & "$openpype_root\tools\create_env.ps1" } else { - Write-Host "OK" -ForegroundColor Green + Write-Color -Text "OK" -Color Green } & "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\tools\fetch_thirdparty_libs.py" diff --git a/tools/make_docs.ps1 b/tools/make_docs.ps1 index 45a11171ae..d356f081de 100644 --- a/tools/make_docs.ps1 +++ b/tools/make_docs.ps1 @@ -44,27 +44,30 @@ $art = @" "@ +$current_dir = Get-Location +$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent +$openpype_root = (Get-Item $script_dir).parent.FullName + +# Install PSWriteColor to support colorized output to terminal +$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell" + Write-Host $art -ForegroundColor DarkGreen -Write-Host ">>> " -NoNewline -ForegroundColor Green -Write-Host "Reading Poetry ... " -NoNewline +Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { - Write-Host "NOT FOUND" -ForegroundColor Yellow - Write-Host "*** " -NoNewline -ForegroundColor Yellow - Write-Host "We need to install Poetry create virtual env first ..." - & "$openpype_root\tools\create_env.ps1" + Write-Color -Text "NOT FOUND" -Color Yellow + Install-Poetry + Write-Color -Text "INSTALLED" -Color Cyan } else { - Write-Host "OK" -ForegroundColor Green + Write-Color -Text "OK" -Color Green } -Write-Host "This will not overwrite existing source rst files, only scan and add new." +Write-Color -Text "... ", "This will not overwrite existing source rst files, only scan and add new." -Color Yellow, Gray Set-Location -Path $openpype_root -Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "Running apidoc ..." +Write-Color -Text ">>> ", "Running apidoc ..." -Color Green, Gray & "$env:POETRY_HOME\bin\poetry" run sphinx-apidoc -M -e -d 10 --ext-intersphinx --ext-todo --ext-coverage --ext-viewcode -o "$($openpype_root)\docs\source" igniter & "$env:POETRY_HOME\bin\poetry" run sphinx-apidoc.exe -M -e -d 10 --ext-intersphinx --ext-todo --ext-coverage --ext-viewcode -o "$($openpype_root)\docs\source" openpype vendor, openpype\vendor -Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "Building html ..." +Write-Color -Text ">>> ", "Building html ..." -Color Green, Gray & "$env:POETRY_HOME\bin\poetry" run python "$($openpype_root)\setup.py" build_sphinx Set-Location -Path $current_dir diff --git a/tools/run_mongo.ps1 b/tools/run_mongo.ps1 index 0f26e86579..a840200252 100644 --- a/tools/run_mongo.ps1 +++ b/tools/run_mongo.ps1 @@ -74,27 +74,20 @@ function Find-Mongo ($preferred_version) { $env:PATH = "$($env:PATH);$($use_version)\bin\" - Write-Host " - auto-added from [ " -NoNewline - Write-Host "$($use_version)\bin\mongod.exe" -NoNewLine -ForegroundColor Cyan - Write-Host " ]" + Write-Color -Text " - auto-added from [ ", "$($use_version)\bin\mongod.exe", " ]" -Color Cyan, White, Cyan return "$($use_version)\bin\mongod.exe" } else { - Write-Host "FAILED " -NoNewLine -ForegroundColor Red - Write-Host "MongoDB not detected" -ForegroundColor Yellow - Write-Host "Tried to find it on standard location " -NoNewline -ForegroundColor Gray - Write-Host " [ " -NoNewline -ForegroundColor Cyan - Write-Host "$($mongoVersions[-1])\bin\mongod.exe" -NoNewline -ForegroundColor White - Write-Host " ] " -NoNewLine -ForegroundColor Cyan - Write-Host "but failed." -ForegroundColor Gray + Write-Color -Text "FAILED " -Color Red -NoNewLine + Write-Color -Text "MongoDB not detected" -Color Yellow + Write-Color -Text "Tried to find it on standard location ", "[ ", "$($mongoVersions[-1])\bin\mongod.exe", " ]", " but failed." -Color Gray, Cyan, White, Cyan, Gray -NoNewline Exit-WithCode 1 } } else { - Write-Host "FAILED " -NoNewLine -ForegroundColor Red - Write-Host "MongoDB not detected in PATH" -ForegroundColor Yellow + Write-Color -Text "FAILED ", "MongoDB not detected in PATH" -Color Red, Yellow Exit-WithCode 1 } } else { - Write-Host "OK" -ForegroundColor Green + Write-Color -Text "OK" -Color Green return Get-Command "mongod" -ErrorAction SilentlyContinue } <# @@ -116,15 +109,7 @@ $dbpath = (Get-Item $openpype_root).parent.FullName + "\mongo_db_data" $preferred_version = "4.0" $mongoPath = Find-Mongo $preferred_version -Write-Host ">>> " -NoNewLine -ForegroundColor Green -Write-Host "Using DB path: " -NoNewLine -Write-Host " [ " -NoNewline -ForegroundColor Cyan -Write-Host "$($dbpath)" -NoNewline -ForegroundColor White -Write-Host " ] "-ForegroundColor Cyan -Write-Host ">>> " -NoNewLine -ForegroundColor Green -Write-Host "Port: " -NoNewLine -Write-Host " [ " -NoNewline -ForegroundColor Cyan -Write-Host "$($port)" -NoNewline -ForegroundColor White -Write-Host " ] " -ForegroundColor Cyan -Start-Process -FilePath $mongopath "--dbpath $($dbpath) --port $($port)" -PassThru | Out-Null +Write-Color -Text ">>> ", "Using DB path: ", "[ ", "$($dbpath)", " ]" -Color Green, Gray, Cyan, White, Cyan +Write-Color -Text ">>> ", "Port: ", "[ ", "$($port)", " ]", -Color Green, Gray, Cyan, White, Cyan +Start-Process -FilePath $mongopath "--dbpath $($dbpath) --port $($port)" -PassThru | Out-Null From e9afd17b296677b1558b83469cdf68cd3cf4e555 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 6 May 2022 10:02:20 +0200 Subject: [PATCH 06/76] converted event to be able create multiple independent event systems --- openpype/lib/events.py | 76 +++++++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 15 deletions(-) diff --git a/openpype/lib/events.py b/openpype/lib/events.py index 7bec6ee30d..3762cec9f9 100644 --- a/openpype/lib/events.py +++ b/openpype/lib/events.py @@ -11,6 +11,10 @@ except Exception: from openpype.lib.python_2_comp import WeakMethod +class MissingEventSystem(Exception): + pass + + class EventCallback(object): """Callback registered to a topic. @@ -179,13 +183,14 @@ class Event(object): """ _data = {} - def __init__(self, topic, data=None, source=None): + def __init__(self, topic, data=None, source=None, event_system=None): self._id = str(uuid4()) self._topic = topic if data is None: data = {} self._data = data self._source = source + self._event_system = event_system def __getitem__(self, key): return self._data[key] @@ -211,28 +216,69 @@ class Event(object): def emit(self): """Emit event and trigger callbacks.""" - StoredCallbacks.emit_event(self) + if self._event_system is None: + raise MissingEventSystem( + "Can't emit event {}. Does not have set event system.".format( + str(repr(self)) + ) + ) + self._event_system.emit_event(self) -class StoredCallbacks: - _registered_callbacks = [] +class EventSystem(object): + def __init__(self): + self._registered_callbacks = [] - @classmethod - def add_callback(cls, topic, callback): + def add_callback(self, topic, callback): callback = EventCallback(topic, callback) - cls._registered_callbacks.append(callback) + self._registered_callbacks.append(callback) return callback - @classmethod - def emit_event(cls, event): + def create_event(self, topic, data, source): + return Event(topic, data, source, self) + + def emit(self, topic, data, source): + event = self.create_event(topic, data, source) + event.emit() + return event + + def emit_event(self, event): invalid_callbacks = [] - for callback in cls._registered_callbacks: + for callback in self._registered_callbacks: callback.process_event(event) if not callback.is_ref_valid: invalid_callbacks.append(callback) for callback in invalid_callbacks: - cls._registered_callbacks.remove(callback) + self._registered_callbacks.remove(callback) + + +class GlobalEvent(Event): + def __init__(self, topic, data=None, source=None): + event_system = GlobalEventSystem.get_global_event_system() + + super(GlobalEvent, self).__init__(topic, data, source, event_system) + + +class GlobalEventSystem: + _global_event_system = None + + @classmethod + def get_global_event_system(cls): + if cls._global_event_system is None: + cls._global_event_system = EventSystem() + return cls._global_event_system + + @classmethod + def add_callback(cls, topic, callback): + event_system = cls.get_global_event_system() + return event_system.add_callback(topic, callback) + + @classmethod + def emit(cls, topic, data, source): + event = GlobalEvent(topic, data, source) + event.emit() + return event def register_event_callback(topic, callback): @@ -249,7 +295,8 @@ def register_event_callback(topic, callback): enable/disable listening to a topic or remove the callback from the topic completely. """ - return StoredCallbacks.add_callback(topic, callback) + + return GlobalEventSystem.add_callback(topic, callback) def emit_event(topic, data=None, source=None): @@ -263,6 +310,5 @@ def emit_event(topic, data=None, source=None): Returns: Event: Object of event that was emitted. """ - event = Event(topic, data, source) - event.emit() - return event + + return GlobalEventSystem.emit(topic, data, source) From dcac9e08a6aae510f024389870a212d0aae1596e Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 7 Jun 2022 15:53:08 +0200 Subject: [PATCH 07/76] :recycle: few tweaks --- tools/create_env.ps1 | 4 +++- tools/fetch_thirdparty_libs.ps1 | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/create_env.ps1 b/tools/create_env.ps1 index 9472c75c2f..b1337b5635 100644 --- a/tools/create_env.ps1 +++ b/tools/create_env.ps1 @@ -169,14 +169,16 @@ if (-not (Test-Path -PathType Leaf -Path "$($openpype_root)\poetry.lock")) { } else { Write-Color -Text ">>> ", "Installing virtual environment from lock." -Color Green, Gray } +$startTime = [int][double]::Parse((Get-Date -UFormat %s)) & "$env:POETRY_HOME\bin\poetry" install --no-root $poetry_verbosity --ansi if ($LASTEXITCODE -ne 0) { Write-Color -Text "!!! ", "Poetry command failed." -Color Red, Yellow Set-Location -Path $current_dir Exit-WithCode 1 } +$endTime = [int][double]::Parse((Get-Date -UFormat %s)) Set-Location -Path $current_dir -New-BurntToastNotification -AppLogo "$openpype_root/openpype/resources/icons/openpype_icon.png" -Text "OpenPype", "Virtual environment created." +New-BurntToastNotification -AppLogo "$openpype_root/openpype/resources/icons/openpype_icon.png" -Text "OpenPype", "Virtual environment created.", "All done in $($endTime - $startTime) secs." Write-Color -Text ">>> ", "Virtual environment created." -Color Green, White diff --git a/tools/fetch_thirdparty_libs.ps1 b/tools/fetch_thirdparty_libs.ps1 index 0226a35bfb..41a3585ff9 100644 --- a/tools/fetch_thirdparty_libs.ps1 +++ b/tools/fetch_thirdparty_libs.ps1 @@ -34,6 +34,8 @@ if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { } else { Write-Color -Text "OK" -Color Green } - +$startTime = [int][double]::Parse((Get-Date -UFormat %s)) & "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\tools\fetch_thirdparty_libs.py" +$endTime = [int][double]::Parse((Get-Date -UFormat %s)) Set-Location -Path $current_dir +New-BurntToastNotification -AppLogo "$openpype_root/openpype/resources/icons/openpype_icon.png" -Text "OpenPype", "Dependencies downloaded", "All done in $($endTime - $startTime) secs." From a080eb00ece263ddb645f7f562934fd8f5364073 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 24 Jun 2022 17:05:45 +0200 Subject: [PATCH 08/76] Convert basic families from old standalone publiser to the new one --- .../project_settings/traypublisher.json | 184 +++++++++++++++++- 1 file changed, 182 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 0b54cfd39e..da5fb2e8b5 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -8,8 +8,8 @@ "default_variants": [ "Main" ], - "description": "Publish workfile backup", - "detailed_description": "", + "description": "Backup of a working scene", + "detailed_description": "Workfiles are full scenes from any application that are directly edited by artists. They represent a state of work on a task at a given point and are usually not directly referenced into other scenes.", "allow_sequences": true, "extensions": [ ".ma", @@ -30,6 +30,186 @@ ".psb", ".aep" ] + }, + { + "family": "model", + "identifier": "", + "label": "Model", + "icon": "fa.cubes", + "default_variants": [ + "Main", + "Proxy", + "Sculpt" + ], + "description": "Clean models", + "detailed_description": "Models should only contain geometry data, without any extras like cameras, locators or bones. It should be ready to be loaded into other scenes as is.\n", + "allow_sequences": false, + "extensions": [ + ".ma", + ".mb", + ".obj", + ".abc", + ".fbx", + ".bgeo", + ".bgeogz", + ".bgeosc", + ".usd", + ".blend" + ] + }, + { + "family": "pointcache", + "identifier": "", + "label": "Pointcache", + "icon": "fa.gears", + "default_variants": [ + "Main" + ], + "description": "Geometry Caches", + "detailed_description": "Alembic or bgeo cache of animated data", + "allow_sequences": true, + "extensions": [ + ".abc", + ".bgeo", + ".bgeogz", + ".bgeosc" + ] + }, + { + "family": "plate", + "identifier": "", + "label": "Plate", + "icon": "mdi.camera-image", + "default_variants": [ + "Main", + "BG", + "Animatic", + "Reference", + "Offline" + ], + "description": "Footage Plates", + "detailed_description": "Any type of image seqeuence coming from outside of the studio. Usually camera footage, but could also be animatics used for reference.", + "allow_sequences": true, + "extensions": [ + ".exr", + ".png", + ".dpx", + ".jpg", + ".tiff", + ".tif", + ".mov", + ".mp4", + ".avi" + ] + }, + { + "family": "render", + "identifier": "", + "label": "Render", + "icon": "mdi.folder-multiple-image", + "default_variants": [], + "description": "Rendered images or video", + "detailed_description": "Sequence or single file renders", + "allow_sequences": true, + "extensions": [ + ".exr", + ".png", + ".dpx", + ".jpg", + ".tiff", + ".tif", + ".mov", + ".mp4", + ".avi" + ] + }, + { + "family": "camera", + "identifier": "", + "label": "Camera", + "icon": "fa.video-camera", + "default_variants": [], + "description": "3d Camera", + "detailed_description": "Ideally this should be only camera itself with baked animation, however, it can technically also include helper geometry.", + "allow_sequences": false, + "extensions": [ + ".abc", + ".ma", + ".hip", + ".blend", + ".fbx", + ".usd" + ] + }, + { + "family": "image", + "identifier": "", + "label": "Image", + "icon": "fa.image", + "default_variants": [ + "Reference", + "Texture", + "Concept", + "Background" + ], + "description": "Single image", + "detailed_description": "Any image data can be published as image family. References, textures, concept art, matte paints. This is a fallback 2d family for everything that doesn't fit more specific family.", + "allow_sequences": false, + "extensions": [ + ".exr", + ".jpg", + ".dpx", + ".bmp", + ".tif", + ".tiff", + ".png", + ".psb", + ".psd" + ] + }, + { + "family": "vdb", + "identifier": "", + "label": "VDB Volumes", + "icon": "fa.cloud", + "default_variants": [], + "description": "Sparse volumetric data", + "detailed_description": "Hierarchical data structure for the efficient storage and manipulation of sparse volumetric data discretized on three-dimensional grids", + "allow_sequences": true, + "extensions": [ + ".vdb" + ] + }, + { + "family": "matchmove", + "identifier": "", + "label": "Matchmove", + "icon": "fa.empire", + "default_variants": [ + "Camera", + "Object", + "Mocap" + ], + "description": "Matchmoving script", + "detailed_description": "Script exported from matchmoving application to be later processed into a tracked camera with additional data", + "allow_sequences": false, + "extensions": [] + }, + { + "family": "rig", + "identifier": "", + "label": "Rig", + "icon": "fa.wheelchair", + "default_variants": [], + "description": "CG rig file", + "detailed_description": "CG rigged character or prop. Rig should be clean of any extra data and directly loadable into it's respective application\t", + "allow_sequences": false, + "extensions": [ + ".ma", + ".blend", + ".hip", + ".hda" + ] } ] } \ No newline at end of file From 5ec1f8c1ccf094bf11d7f9e9bd722d4606e0d127 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 24 Jun 2022 17:47:28 +0200 Subject: [PATCH 09/76] add unreal texture and tweak model help --- .../defaults/project_settings/traypublisher.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index da5fb2e8b5..5afaaee78c 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -42,7 +42,7 @@ "Sculpt" ], "description": "Clean models", - "detailed_description": "Models should only contain geometry data, without any extras like cameras, locators or bones. It should be ready to be loaded into other scenes as is.\n", + "detailed_description": "Models should only contain geometry data, without any extras like cameras, locators or bones.\n\nKeep in mind that models published from tray publisher are not validated for correctness. ", "allow_sequences": false, "extensions": [ ".ma", @@ -210,6 +210,19 @@ ".hip", ".hda" ] + }, + { + "family": "simpleUnrealTexture", + "identifier": "", + "label": "Simple UE texture", + "icon": "fa.image", + "default_variants": [ + "" + ], + "description": "Simple Unreal Engine texture", + "detailed_description": "Texture files with Unreal Engine naming conventions", + "allow_sequences": false, + "extensions": [] } ] } \ No newline at end of file From 64d80f2d05c7639505db50a9c512d01d6e1cf224 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Jul 2022 13:40:24 +0200 Subject: [PATCH 10/76] nuke: adding retime loading option to clip loader --- openpype/hosts/nuke/plugins/load/load_clip.py | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index d177e6ba76..e9530c58c0 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -55,7 +55,8 @@ class LoadClip(plugin.NukeLoader): # option gui defaults = { - "start_at_workfile": True + "start_at_workfile": True, + "add_retime": True } options = [ @@ -63,6 +64,11 @@ class LoadClip(plugin.NukeLoader): "start_at_workfile", help="Load at workfile start frame", default=True + ), + qargparse.Boolean( + "add_retime", + help="Load with retime", + default=True ) ] @@ -88,6 +94,9 @@ class LoadClip(plugin.NukeLoader): start_at_workfile = options.get( "start_at_workfile", self.defaults["start_at_workfile"]) + add_retime = options.get( + "add_retime", self.defaults["add_retime"]) + version = context['version'] version_data = version.get("data", {}) repre_id = repre["_id"] @@ -151,7 +160,7 @@ class LoadClip(plugin.NukeLoader): data_imprint = {} for k in add_keys: if k == 'version': - data_imprint.update({k: context["version"]['name']}) + data_imprint[k] = context["version"]['name'] elif k == 'colorspace': colorspace = repre["data"].get(k) colorspace = colorspace or version_data.get(k) @@ -159,10 +168,13 @@ class LoadClip(plugin.NukeLoader): if used_colorspace: data_imprint["used_colorspace"] = used_colorspace else: - data_imprint.update( - {k: context["version"]['data'].get(k, str(None))}) + data_imprint[k] = context["version"]['data'].get( + k, str(None)) - data_imprint.update({"objectName": read_name}) + data_imprint["objectName"] = read_name + + if add_retime and version_data.get("retime", None): + data_imprint["addRetime"] = True read_node["tile_color"].setValue(int("0x4ecd25ff", 16)) @@ -174,7 +186,7 @@ class LoadClip(plugin.NukeLoader): loader=self.__class__.__name__, data=data_imprint) - if version_data.get("retime", None): + if add_retime and version_data.get("retime", None): self._make_retimes(read_node, version_data) self.set_as_member(read_node) @@ -200,6 +212,10 @@ class LoadClip(plugin.NukeLoader): start_at_workfile = bool("start at" in read_node['frame_mode'].value()) + # TODO: find `addRetime` add openpipe data + # add_retime = options.get( + # "add_retime", self.defaults["add_retime"]) + project_name = legacy_io.active_project() version_doc = get_version_by_id(project_name, representation["parent"]) @@ -286,7 +302,7 @@ class LoadClip(plugin.NukeLoader): "updated to version: {}".format(version_doc.get("name")) ) - if version_data.get("retime", None): + if add_retime and version_data.get("retime", None): self._make_retimes(read_node, version_data) else: self.clear_members(read_node) From dfb041d8524fe8b1cc415a5be86a49cb0148f529 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 15:06:46 +0200 Subject: [PATCH 11/76] timers manager is using client query functions --- .../modules/timers_manager/timers_manager.py | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/openpype/modules/timers_manager/timers_manager.py b/openpype/modules/timers_manager/timers_manager.py index 3cf1614316..3453e4bc4c 100644 --- a/openpype/modules/timers_manager/timers_manager.py +++ b/openpype/modules/timers_manager/timers_manager.py @@ -2,13 +2,13 @@ import os import platform +from openpype.client import get_asset_by_name from openpype.modules import OpenPypeModule from openpype_interfaces import ( ITrayService, ILaunchHookPaths ) from openpype.lib.events import register_event_callback -from openpype.pipeline import AvalonMongoDB from .exceptions import InvalidContextError @@ -197,22 +197,13 @@ class TimersManager(OpenPypeModule, ITrayService, ILaunchHookPaths): " Project: \"{}\" Asset: \"{}\" Task: \"{}\"" ).format(str(project_name), str(asset_name), str(task_name))) - dbconn = AvalonMongoDB() - dbconn.install() - dbconn.Session["AVALON_PROJECT"] = project_name - - asset_doc = dbconn.find_one( - { - "type": "asset", - "name": asset_name - }, - { - "data.tasks": True, - "data.parents": True - } + asset_doc = get_asset_by_name( + project_name, + asset_name, + fields=["_id", "name", "data.tasks", "data.parents"] ) + if not asset_doc: - dbconn.uninstall() raise InvalidContextError(( "Asset \"{}\" not found in project \"{}\"" ).format(asset_name, project_name)) @@ -220,7 +211,6 @@ class TimersManager(OpenPypeModule, ITrayService, ILaunchHookPaths): asset_data = asset_doc.get("data") or {} asset_tasks = asset_data.get("tasks") or {} if task_name not in asset_tasks: - dbconn.uninstall() raise InvalidContextError(( "Task \"{}\" not found on asset \"{}\" in project \"{}\"" ).format(task_name, asset_name, project_name)) @@ -238,9 +228,10 @@ class TimersManager(OpenPypeModule, ITrayService, ILaunchHookPaths): hierarchy_items = asset_data.get("parents") or [] hierarchy_items.append(asset_name) - dbconn.uninstall() return { "project_name": project_name, + "asset_id": str(asset_doc["_id"]), + "asset_name": asset_doc["name"], "task_name": task_name, "task_type": task_type, "hierarchy": hierarchy_items From 498ed608ebce7d639fcc091da979307f0f21da62 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Jul 2022 16:24:22 +0200 Subject: [PATCH 12/76] nuke: setting loader option defaults from settings --- openpype/hosts/nuke/plugins/load/load_clip.py | 36 ++++++++------- .../defaults/project_settings/nuke.json | 6 ++- .../schemas/schema_nuke_load.json | 46 ++++++++++++++++++- 3 files changed, 68 insertions(+), 20 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index e9530c58c0..43dd5a66eb 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -54,26 +54,28 @@ class LoadClip(plugin.NukeLoader): script_start = int(nuke.root()["first_frame"].value()) # option gui - defaults = { + options_defaults = { "start_at_workfile": True, "add_retime": True } - options = [ - qargparse.Boolean( - "start_at_workfile", - help="Load at workfile start frame", - default=True - ), - qargparse.Boolean( - "add_retime", - help="Load with retime", - default=True - ) - ] - node_name_template = "{class_name}_{ext}" + @classmethod + def get_options(cls, *args): + return [ + qargparse.Boolean( + "start_at_workfile", + help="Load at workfile start frame", + default=cls.options_defaults["start_at_workfile"] + ), + qargparse.Boolean( + "add_retime", + help="Load with retime", + default=cls.options_defaults["add_retime"] + ) + ] + @classmethod def get_representations(cls): return ( @@ -92,10 +94,10 @@ class LoadClip(plugin.NukeLoader): file = self.fname.replace("\\", "/") start_at_workfile = options.get( - "start_at_workfile", self.defaults["start_at_workfile"]) + "start_at_workfile", self.options_defaults["start_at_workfile"]) add_retime = options.get( - "add_retime", self.defaults["add_retime"]) + "add_retime", self.options_defaults["add_retime"]) version = context['version'] version_data = version.get("data", {}) @@ -214,7 +216,7 @@ class LoadClip(plugin.NukeLoader): # TODO: find `addRetime` add openpipe data # add_retime = options.get( - # "add_retime", self.defaults["add_retime"]) + # "add_retime", self.options_defaults["add_retime"]) project_name = legacy_io.active_project() version_doc = get_version_by_id(project_name, representation["parent"]) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 6c45e2a9c1..3e29122074 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -287,7 +287,11 @@ "LoadClip": { "enabled": true, "_representations": [], - "node_name_template": "{class_name}_{ext}" + "node_name_template": "{class_name}_{ext}", + "options_defaults": { + "start_at_workfile": true, + "add_retime": true + } } }, "workfile_builder": { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json index 5bd8337e4c..805424c632 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json @@ -11,10 +11,52 @@ { "key": "LoadImage", "label": "Image Loader" + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "LoadClip", + "label": "Clip Loader", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" }, { - "key": "LoadClip", - "label": "Clip Loader" + "type": "list", + "key": "_representations", + "label": "Representations", + "object_type": "text" + }, + { + "type": "text", + "key": "node_name_template", + "label": "Node name template" + }, + { + "type": "splitter" + }, + { + "type": "dict", + "collapsible": false, + "key": "options_defaults", + "label": "Loader option defaults", + "children": [ + { + "type": "boolean", + "key": "start_at_workfile", + "label": "Start at worfile beggining" + }, + { + "type": "boolean", + "key": "add_retime", + "label": "Add retime" + } + ] } ] } From d04e95e28f9a206c4a7390a7a1c82f418736b7c9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Jul 2022 16:48:39 +0200 Subject: [PATCH 13/76] nuke: updating clip with retime options --- openpype/hosts/nuke/plugins/load/load_clip.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index 43dd5a66eb..b2dc4a52d7 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -212,11 +212,12 @@ class LoadClip(plugin.NukeLoader): read_node = nuke.toNode(container['objectName']) file = get_representation_path(representation).replace("\\", "/") - start_at_workfile = bool("start at" in read_node['frame_mode'].value()) + start_at_workfile = "start at" in read_node['frame_mode'].value() - # TODO: find `addRetime` add openpipe data - # add_retime = options.get( - # "add_retime", self.options_defaults["add_retime"]) + add_retime = [ + key for key in read_node.knobs().keys() + if "addRetime" in key + ] project_name = legacy_io.active_project() version_doc = get_version_by_id(project_name, representation["parent"]) From 9d4d8873358bcb6fde08e46817f47a09d858c68d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 17:13:50 +0200 Subject: [PATCH 14/76] keep mismatch target plugins in report --- openpype/pipeline/create/context.py | 10 ++++++++++ openpype/tools/publisher/control.py | 15 +++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 7f0341c127..ac345ea47b 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -709,6 +709,7 @@ class CreateContext: self.manual_creators = {} self.publish_discover_result = None + self.publish_plugins_mismatch_targets = [] self.publish_plugins = [] self.plugins_with_defs = [] self._attr_plugins_by_family = {} @@ -831,6 +832,7 @@ class CreateContext: discover_result = DiscoverResult() plugins_with_defs = [] plugins_by_targets = [] + plugins_mismatch_targets = [] if discover_publish_plugins: discover_result = publish_plugins_discover() publish_plugins = discover_result.plugins @@ -840,11 +842,19 @@ class CreateContext: plugins_by_targets = pyblish.logic.plugins_by_targets( publish_plugins, list(targets) ) + # Collect plugins that can have attribute definitions for plugin in publish_plugins: if OpenPypePyblishPluginMixin in inspect.getmro(plugin): plugins_with_defs.append(plugin) + plugins_mismatch_targets = [ + plugin + for plugin in publish_plugins + if plugin not in plugins_by_targets + ] + + self.publish_plugins_mismatch_targets = plugins_mismatch_targets self.publish_discover_result = discover_result self.publish_plugins = plugins_by_targets self.plugins_with_defs = plugins_with_defs diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 915fb7f32e..f692bb4000 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -154,15 +154,20 @@ class PublishReport: self._all_instances_by_id = {} self._current_context = None - def reset(self, context, publish_discover_result=None): + def reset(self, context, create_context): """Reset report and clear all data.""" - self._publish_discover_result = publish_discover_result + + self._publish_discover_result = create_context.publish_discover_result self._plugin_data = [] self._plugin_data_with_plugin = [] self._current_plugin_data = {} self._all_instances_by_id = {} self._current_context = context + for plugin in create_context.publish_plugins_mismatch_targets: + plugin_data = self._add_plugin_data_item(plugin) + plugin_data["skipped"] = True + def add_plugin_iter(self, plugin, context): """Add report about single iteration of plugin.""" for instance in context: @@ -205,6 +210,7 @@ class PublishReport: "name": plugin.__name__, "label": label, "order": plugin.order, + "targets": list(plugin.targets), "instances_data": [], "actions_data": [], "skipped": False, @@ -777,10 +783,7 @@ class PublisherController: # - pop the key after first collector using it would be safest option? self._publish_context.data["create_context"] = self.create_context - self._publish_report.reset( - self._publish_context, - self.create_context.publish_discover_result - ) + self._publish_report.reset(self._publish_context, self.create_context) self._publish_validation_errors = [] self._publish_current_plugin_validation_errors = None self._publish_error = None From 8a951ee60f837dbce81442574f922691707ce614 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 17:27:47 +0200 Subject: [PATCH 15/76] removed GlobalEvent --- openpype/lib/events.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/openpype/lib/events.py b/openpype/lib/events.py index 3762cec9f9..4a7d648a7e 100644 --- a/openpype/lib/events.py +++ b/openpype/lib/events.py @@ -253,13 +253,6 @@ class EventSystem(object): self._registered_callbacks.remove(callback) -class GlobalEvent(Event): - def __init__(self, topic, data=None, source=None): - event_system = GlobalEventSystem.get_global_event_system() - - super(GlobalEvent, self).__init__(topic, data, source, event_system) - - class GlobalEventSystem: _global_event_system = None @@ -276,7 +269,7 @@ class GlobalEventSystem: @classmethod def emit(cls, topic, data, source): - event = GlobalEvent(topic, data, source) + event = Event(topic, data, source, cls.get_global_event_system()) event.emit() return event From 8ab6b41db5689312c39e07426fda869d9c2cc421 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 17:34:51 +0200 Subject: [PATCH 16/76] simplified global event emit --- openpype/lib/events.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/lib/events.py b/openpype/lib/events.py index 4a7d648a7e..215e36bc4e 100644 --- a/openpype/lib/events.py +++ b/openpype/lib/events.py @@ -180,7 +180,10 @@ class Event(object): topic (str): Identifier of event. data (Any): Data specific for event. Dictionary is recommended. source (str): Identifier of source. + event_system (EventSystem): Event system in which can be event + triggered. """ + _data = {} def __init__(self, topic, data=None, source=None, event_system=None): @@ -269,9 +272,8 @@ class GlobalEventSystem: @classmethod def emit(cls, topic, data, source): - event = Event(topic, data, source, cls.get_global_event_system()) - event.emit() - return event + event_system = cls.get_global_event_system() + return event_system.emit(topic, data, source) def register_event_callback(topic, callback): From 804bb9b3382970088581f07f2a3492f0fa59bfe7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 17:48:55 +0200 Subject: [PATCH 17/76] fix group accessing --- openpype/tools/publisher/widgets/card_view_widgets.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index b6fcee7edb..fc8bb2af10 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -98,6 +98,7 @@ class GroupWidget(QtWidgets.QWidget): instances(list): List of instances in CreateContext. """ + # Store instances by id and by subset name instances_by_id = {} instances_by_subset_name = collections.defaultdict(list) @@ -142,6 +143,7 @@ class GroupWidget(QtWidgets.QWidget): class CardWidget(BaseClickableFrame): """Clickable card used as bigger button.""" + selected = QtCore.Signal(str, str) # Group identifier of card # - this must be set because if send when mouse is released with card id @@ -178,6 +180,7 @@ class ContextCardWidget(CardWidget): Is not visually under group widget and is always at the top of card view. """ + def __init__(self, parent): super(ContextCardWidget, self).__init__(parent) @@ -204,13 +207,14 @@ class ContextCardWidget(CardWidget): class InstanceCardWidget(CardWidget): """Card widget representing instance.""" + active_changed = QtCore.Signal() def __init__(self, instance, group_icon, parent): super(InstanceCardWidget, self).__init__(parent) self._id = instance.id - self._group_identifier = instance.creator_label + self._group_identifier = instance.group_label self._group_icon = group_icon self.instance = instance From fde803e6ef4de6c33503e6dc8cc785ae2a9d9649 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 17:49:10 +0200 Subject: [PATCH 18/76] set line edit on comboboxes --- openpype/widgets/attribute_defs/widgets.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py index b6493b80a8..63d40e2df1 100644 --- a/openpype/widgets/attribute_defs/widgets.py +++ b/openpype/widgets/attribute_defs/widgets.py @@ -374,6 +374,10 @@ class EnumAttrWidget(_BaseAttrDefWidget): combo_delegate = QtWidgets.QStyledItemDelegate(input_widget) input_widget.setItemDelegate(combo_delegate) + line_edit = QtWidgets.QLineEdit(input_widget) + line_edit.setReadOnly(True) + input_widget.setLineEdit(line_edit) + if self.attr_def.tooltip: input_widget.setToolTip(self.attr_def.tooltip) @@ -408,7 +412,8 @@ class EnumAttrWidget(_BaseAttrDefWidget): self._input_widget.setCurrentIndex(idx) else: - self._input_widget.lineEdit().setText("Multiselection") + line_edit = self._input_widget.lineEdit() + line_edit.setText("Multiselection") class UnknownAttrWidget(_BaseAttrDefWidget): From 018896f9239f3fb3a036512a892a8f48593dca85 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 18:28:03 +0200 Subject: [PATCH 19/76] added docstrings --- openpype/lib/events.py | 57 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/openpype/lib/events.py b/openpype/lib/events.py index 215e36bc4e..301d62e2a6 100644 --- a/openpype/lib/events.py +++ b/openpype/lib/events.py @@ -229,23 +229,74 @@ class Event(object): class EventSystem(object): + """Encapsulate event handling into an object. + + System wraps registered callbacks and triggered events into single object + so it is possible to create mutltiple independent systems that have their + topics and callbacks. + + + """ + def __init__(self): self._registered_callbacks = [] def add_callback(self, topic, callback): + """Register callback in event system. + + Args: + topic (str): Topic for EventCallback. + callback (Callable): Function or method that will be called + when topic is triggered. + + Returns: + EventCallback: Created callback object which can be used to + stop listening. + """ + callback = EventCallback(topic, callback) self._registered_callbacks.append(callback) return callback def create_event(self, topic, data, source): + """Create new event which is bound to event system. + + Args: + topic (str): Event topic. + data (dict): Data related to event. + source (str): Source of event. + + Returns: + Event: Object of event. + """ + return Event(topic, data, source, self) def emit(self, topic, data, source): + """Create event based on passed data and emit it. + + This is easiest way how to trigger event in an event system. + + Args: + topic (str): Event topic. + data (dict): Data related to event. + source (str): Source of event. + + Returns: + Event: Created and emitted event. + """ + event = self.create_event(topic, data, source) event.emit() return event def emit_event(self, event): + """Emit event object. + + Args: + event (Event): Prepared event with topic and data. + """ + invalid_callbacks = [] for callback in self._registered_callbacks: callback.process_event(event) @@ -257,6 +308,12 @@ class EventSystem(object): class GlobalEventSystem: + """Event system living in global scope of process. + + This is primarily used in host implementation to trigger events + related to DCC changes or changes of context in the host implementation. + """ + _global_event_system = None @classmethod From 77ffca938a7cd4a3600dd8167bf4e2f346c11fd7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 18:53:58 +0200 Subject: [PATCH 20/76] make enum line edit transparent for mouse --- openpype/widgets/attribute_defs/widgets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py index 63d40e2df1..7a7035317b 100644 --- a/openpype/widgets/attribute_defs/widgets.py +++ b/openpype/widgets/attribute_defs/widgets.py @@ -376,6 +376,7 @@ class EnumAttrWidget(_BaseAttrDefWidget): line_edit = QtWidgets.QLineEdit(input_widget) line_edit.setReadOnly(True) + line_edit.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents) input_widget.setLineEdit(line_edit) if self.attr_def.tooltip: @@ -413,7 +414,7 @@ class EnumAttrWidget(_BaseAttrDefWidget): else: line_edit = self._input_widget.lineEdit() - line_edit.setText("Multiselection") + line_edit.setText("< Multiselection> ") class UnknownAttrWidget(_BaseAttrDefWidget): From 81469cbc54126487a1d3e35d78d36294a966ed14 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 12 Jul 2022 23:25:44 +0200 Subject: [PATCH 21/76] implemented combobox that can have custom text --- openpype/tools/utils/__init__.py | 2 ++ openpype/tools/utils/widgets.py | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/openpype/tools/utils/__init__.py b/openpype/tools/utils/__init__.py index 0f367510bd..5ccc1b40b3 100644 --- a/openpype/tools/utils/__init__.py +++ b/openpype/tools/utils/__init__.py @@ -1,4 +1,5 @@ from .widgets import ( + CustomTextComboBox, PlaceholderLineEdit, BaseClickableFrame, ClickableFrame, @@ -28,6 +29,7 @@ from .overlay_messages import ( __all__ = ( + "CustomTextComboBox", "PlaceholderLineEdit", "BaseClickableFrame", "ClickableFrame", diff --git a/openpype/tools/utils/widgets.py b/openpype/tools/utils/widgets.py index d5ae909be8..df0d349822 100644 --- a/openpype/tools/utils/widgets.py +++ b/openpype/tools/utils/widgets.py @@ -11,6 +11,28 @@ from openpype.style import ( log = logging.getLogger(__name__) +class CustomTextComboBox(QtWidgets.QComboBox): + """Combobox which can have different text showed.""" + + def __init__(self, *args, **kwargs): + self._custom_text = None + super(CustomTextComboBox, self).__init__(*args, **kwargs) + + def set_custom_text(self, text=None): + if self._custom_text != text: + self._custom_text = text + self.repaint() + + def paintEvent(self, event): + painter = QtWidgets.QStylePainter(self) + option = QtWidgets.QStyleOptionComboBox() + self.initStyleOption(option) + if self._custom_text is not None: + option.currentText = self._custom_text + painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, option) + painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, option) + + class PlaceholderLineEdit(QtWidgets.QLineEdit): """Set placeholder color of QLineEdit in Qt 5.12 and higher.""" def __init__(self, *args, **kwargs): From cc893a64b44a297f218050e816a37bd6e1d9f583 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 12 Jul 2022 23:26:09 +0200 Subject: [PATCH 22/76] use combobox with custom text in EnumAttrWidget --- openpype/widgets/attribute_defs/widgets.py | 24 ++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py index 7a7035317b..e4c4aba170 100644 --- a/openpype/widgets/attribute_defs/widgets.py +++ b/openpype/widgets/attribute_defs/widgets.py @@ -15,6 +15,7 @@ from openpype.lib.attribute_definitions import ( UISeparatorDef, UILabelDef ) +from openpype.tools.utils import CustomTextComboBox from openpype.widgets.nice_checkbox import NiceCheckbox from .files_widget import FilesWidget @@ -369,8 +370,12 @@ class BoolAttrWidget(_BaseAttrDefWidget): class EnumAttrWidget(_BaseAttrDefWidget): + def __init__(self, *args, **kwargs): + self._multivalue = False + super(EnumAttrWidget, self).__init__(*args, **kwargs) + def _ui_init(self): - input_widget = QtWidgets.QComboBox(self) + input_widget = CustomTextComboBox(self) combo_delegate = QtWidgets.QStyledItemDelegate(input_widget) input_widget.setItemDelegate(combo_delegate) @@ -399,6 +404,9 @@ class EnumAttrWidget(_BaseAttrDefWidget): def _on_value_change(self): new_value = self.current_value() + if self._multivalue: + self._multivalue = False + self._input_widget.set_custom_text(None) self.value_changed.emit(new_value, self.attr_def.id) def current_value(self): @@ -406,15 +414,23 @@ class EnumAttrWidget(_BaseAttrDefWidget): return self._input_widget.itemData(idx) def set_value(self, value, multivalue=False): + if multivalue: + set_value = set(value) + if len(set_value) == 1: + multivalue = False + value = tuple(set_value)[0] + if not multivalue: idx = self._input_widget.findData(value) cur_idx = self._input_widget.currentIndex() if idx != cur_idx and idx >= 0: self._input_widget.setCurrentIndex(idx) - else: - line_edit = self._input_widget.lineEdit() - line_edit.setText("< Multiselection> ") + custom_text = None + if multivalue: + custom_text = "< Multiselection >" + self._input_widget.set_custom_text(custom_text) + self._multivalue = multivalue class UnknownAttrWidget(_BaseAttrDefWidget): From 9811e8a1d5dc586e7b3e7786e03bc5c0ef2f3974 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 12 Jul 2022 23:37:51 +0200 Subject: [PATCH 23/76] fix empty line --- openpype/tools/publisher/widgets/card_view_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index fc8bb2af10..04df85b0fb 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -98,7 +98,7 @@ class GroupWidget(QtWidgets.QWidget): instances(list): List of instances in CreateContext. """ - + # Store instances by id and by subset name instances_by_id = {} instances_by_subset_name = collections.defaultdict(list) From c9e714a7afc37f98c9cb4792fa42b53b1b2272c3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Jul 2022 12:00:21 +0200 Subject: [PATCH 24/76] implemented tray publisher host using HostBase --- openpype/hosts/traypublisher/api/__init__.py | 16 +----- openpype/hosts/traypublisher/api/pipeline.py | 60 ++++++++++---------- openpype/tools/traypublisher/window.py | 13 +++-- 3 files changed, 41 insertions(+), 48 deletions(-) diff --git a/openpype/hosts/traypublisher/api/__init__.py b/openpype/hosts/traypublisher/api/__init__.py index c461c0c526..4e7284b09a 100644 --- a/openpype/hosts/traypublisher/api/__init__.py +++ b/openpype/hosts/traypublisher/api/__init__.py @@ -1,20 +1,8 @@ from .pipeline import ( - install, - ls, - - set_project_name, - get_context_title, - get_context_data, - update_context_data, + TrayPublisherHost, ) __all__ = ( - "install", - "ls", - - "set_project_name", - "get_context_title", - "get_context_data", - "update_context_data", + "TrayPublisherHost", ) diff --git a/openpype/hosts/traypublisher/api/pipeline.py b/openpype/hosts/traypublisher/api/pipeline.py index 954a0bae47..2d9db7801e 100644 --- a/openpype/hosts/traypublisher/api/pipeline.py +++ b/openpype/hosts/traypublisher/api/pipeline.py @@ -9,6 +9,8 @@ from openpype.pipeline import ( register_creator_plugin_path, legacy_io, ) +from openpype.host import HostBase, INewPublisher + ROOT_DIR = os.path.dirname(os.path.dirname( os.path.abspath(__file__) @@ -17,6 +19,35 @@ PUBLISH_PATH = os.path.join(ROOT_DIR, "plugins", "publish") CREATE_PATH = os.path.join(ROOT_DIR, "plugins", "create") +class TrayPublisherHost(HostBase, INewPublisher): + name = "traypublisher" + + def install(self): + os.environ["AVALON_APP"] = self.name + legacy_io.Session["AVALON_APP"] = self.name + + pyblish.api.register_host("traypublisher") + pyblish.api.register_plugin_path(PUBLISH_PATH) + register_creator_plugin_path(CREATE_PATH) + + def get_context_title(self): + return HostContext.get_project_name() + + def get_context_data(self): + return HostContext.get_context_data() + + def update_context_data(self, data, changes): + HostContext.save_context_data(data, changes) + + def set_project_name(self, project_name): + # TODO Deregister project specific plugins and register new project + # plugins + os.environ["AVALON_PROJECT"] = project_name + legacy_io.Session["AVALON_PROJECT"] = project_name + legacy_io.install() + HostContext.set_project_name(project_name) + + class HostContext: _context_json_path = None @@ -150,32 +181,3 @@ def get_context_data(): def update_context_data(data, changes): HostContext.save_context_data(data) - - -def get_context_title(): - return HostContext.get_project_name() - - -def ls(): - """Probably will never return loaded containers.""" - return [] - - -def install(): - """This is called before a project is known. - - Project is defined with 'set_project_name'. - """ - os.environ["AVALON_APP"] = "traypublisher" - - pyblish.api.register_host("traypublisher") - pyblish.api.register_plugin_path(PUBLISH_PATH) - register_creator_plugin_path(CREATE_PATH) - - -def set_project_name(project_name): - # TODO Deregister project specific plugins and register new project plugins - os.environ["AVALON_PROJECT"] = project_name - legacy_io.Session["AVALON_PROJECT"] = project_name - legacy_io.install() - HostContext.set_project_name(project_name) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index 5934c4aa8a..cc33287091 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -12,9 +12,7 @@ from openpype.pipeline import ( install_host, AvalonMongoDB, ) -from openpype.hosts.traypublisher import ( - api as traypublisher -) +from openpype.hosts.traypublisher.api import TrayPublisherHost from openpype.tools.publisher import PublisherWindow from openpype.tools.utils.constants import PROJECT_NAME_ROLE from openpype.tools.utils.models import ( @@ -111,9 +109,13 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): if project_name: self._set_project(project_name) + @property + def host(self): + return self._publisher_window.controller.host + def _set_project(self, project_name): self._project_name = project_name - traypublisher.set_project_name(project_name) + self.host.set_project_name(project_name) self.setVisible(False) self.project_selected.emit(project_name) @@ -190,7 +192,8 @@ class TrayPublishWindow(PublisherWindow): def main(): - install_host(traypublisher) + host = TrayPublisherHost() + install_host(host) app = QtWidgets.QApplication([]) window = TrayPublishWindow() window.show() From 74cd74f053023324a9a6c46e67cfd5023147cda6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Jul 2022 12:00:29 +0200 Subject: [PATCH 25/76] creatos have access to host --- openpype/pipeline/create/creator_plugins.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 91b9d80234..52c76db5ef 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -102,6 +102,10 @@ class BaseCreator: return self.create_context.project_name + @property + def host(self): + return self.create_context.host + def get_group_label(self): """Group label under which are instances grouped in UI. From 2fbe33750df3c25b1089208bf093326720fec21e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Jul 2022 12:01:55 +0200 Subject: [PATCH 26/76] implemented helper method to store new instance --- openpype/hosts/traypublisher/api/plugin.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 202664cfc6..cc93d7c157 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -37,6 +37,21 @@ class TrayPublishCreator(Creator): # Use same attributes as for instance attrobites return self.get_instance_attr_defs() + def _store_new_instance(self, new_instance): + """Tray publisher specific method to store instance. + + Instance is stored into "workfile" of traypublisher and also add it + to CreateContext. + + Args: + new_instance (CreatedInstance): Instance that should be stored. + """ + + # Host implementation of storing metadata about instance + HostContext.add_instance(new_instance.data_to_store()) + # Add instance to current context + self._add_instance_to_context(new_instance) + class SettingsCreator(TrayPublishCreator): create_allow_context_change = True @@ -58,10 +73,8 @@ class SettingsCreator(TrayPublishCreator): data["settings_creator"] = True # Create new instance new_instance = CreatedInstance(self.family, subset_name, data, self) - # Host implementation of storing metadata about instance - HostContext.add_instance(new_instance.data_to_store()) - # Add instance to current context - self._add_instance_to_context(new_instance) + + self._store_new_instance(new_instance) def get_instance_attr_defs(self): return [ From 9d6eb0d5c2efdbfbee8b40aac3e19cef87f9973e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Jul 2022 12:30:09 +0200 Subject: [PATCH 27/76] added extract to file action to project list context actions --- openpype/tools/settings/settings/categories.py | 3 +++ openpype/tools/settings/settings/widgets.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index 764f42f1a3..f42027d9e2 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -854,6 +854,9 @@ class ProjectWidget(SettingsCategoryWidget): project_list_widget.version_change_requested.connect( self._on_source_version_change ) + project_list_widget.extract_to_file_requested.connect( + self._on_extract_to_file + ) self.project_list_widget = project_list_widget diff --git a/openpype/tools/settings/settings/widgets.py b/openpype/tools/settings/settings/widgets.py index 45c21d5685..1d94094897 100644 --- a/openpype/tools/settings/settings/widgets.py +++ b/openpype/tools/settings/settings/widgets.py @@ -1008,6 +1008,7 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel): class ProjectListWidget(QtWidgets.QWidget): project_changed = QtCore.Signal() version_change_requested = QtCore.Signal(str) + extract_to_file_requested = QtCore.Signal() def __init__(self, parent, only_active=False): self._parent = parent @@ -1099,6 +1100,10 @@ class ProjectListWidget(QtWidgets.QWidget): self.version_change_requested ) submenu.addAction(action) + + extract_action = QtWidgets.QAction("Extract to file", menu) + extract_action.triggered.connect(self.extract_to_file_requested) + menu.addMenu(submenu) menu.exec_(QtGui.QCursor.pos()) From 4e137c1f4bd88ad35b4df733a72f26d590c80284 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Jul 2022 12:33:31 +0200 Subject: [PATCH 28/76] add action to menu --- openpype/tools/settings/settings/widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/settings/settings/widgets.py b/openpype/tools/settings/settings/widgets.py index 1d94094897..88d923c16a 100644 --- a/openpype/tools/settings/settings/widgets.py +++ b/openpype/tools/settings/settings/widgets.py @@ -1105,6 +1105,7 @@ class ProjectListWidget(QtWidgets.QWidget): extract_action.triggered.connect(self.extract_to_file_requested) menu.addMenu(submenu) + menu.addAction(extract_action) menu.exec_(QtGui.QCursor.pos()) def on_item_clicked(self, new_index): From f6129b5f5b84d03816e17cfd6489df4d4d300116 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Jul 2022 12:41:15 +0200 Subject: [PATCH 29/76] trigger 'openpype.project.structure.created' topic on finish of create project structure action --- .../event_handlers_user/action_create_project_structure.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py b/openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py index ebea8872f9..df914de854 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py +++ b/openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py @@ -84,6 +84,11 @@ class CreateProjectFolders(BaseAction): create_project_folders(basic_paths, project_name) self.create_ftrack_entities(basic_paths, project_entity) + self.trigger_event( + "openpype.project.structure.created", + {"project_name": project_name} + ) + except Exception as exc: self.log.warning("Creating of structure crashed.", exc_info=True) session.rollback() From 450a20dcca1b74d01c23beebdd170b356a8ccb34 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 13 Jul 2022 15:28:01 +0200 Subject: [PATCH 30/76] OP-3481 - add source key to Note formatting Allows to fill value from instance.data["source"] as a {source} in Ftrack Note. --- openpype/modules/ftrack/plugins/publish/integrate_ftrack_note.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_note.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_note.py index 952b21546d..77a7ebdfcf 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_note.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_note.py @@ -116,6 +116,7 @@ class IntegrateFtrackNote(pyblish.api.InstancePlugin): "app_name": app_name, "app_label": app_label, "published_paths": "
".join(sorted(published_paths)), + "source": instance.data.get("source", '') } comment = template.format(**format_data) if not comment: From 5d0e68385e13f8f69397a8f78c4c179307ffe1fe Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 13 Jul 2022 15:28:15 +0200 Subject: [PATCH 31/76] OP-3481 - add source key to Note formatting Allows to fill value from instance.data["source"] as a {source} in Ftrack Note. --- .../entities/schemas/projects_schema/schema_project_ftrack.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json index f8f9d5093d..c0069dcdab 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json @@ -822,7 +822,7 @@ }, { "type": "label", - "label": "Template may contain formatting keys intent, comment, host_name, app_name, app_label and published_paths." + "label": "Template may contain formatting keys intent, comment, host_name, app_name, app_label, published_paths and source." }, { "type": "text", From 0047ca458c3e6febe9b9826fd8938bea4c05339d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 13 Jul 2022 15:30:10 +0200 Subject: [PATCH 32/76] OP-3481 - simple creators fill 'source' key Source filepats used to fill source value used later in Ftrack note or version in DB. --- .../traypublisher/plugins/publish/collect_simple_instances.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index b2be43c701..2fb6fcf3ce 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -45,6 +45,8 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): "files": filenames }) + instance.data["source"] = filepaths + self.log.debug("Created Simple Settings instance {}".format( instance.data )) From dbcf9097d38d8d7a4b094e161291859905ef3a3d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 13 Jul 2022 15:43:22 +0200 Subject: [PATCH 33/76] OP-3481 - fix source, must be string --- .../traypublisher/plugins/publish/collect_simple_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index 2fb6fcf3ce..1f473ff71c 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -45,7 +45,7 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): "files": filenames }) - instance.data["source"] = filepaths + instance.data["source"] = "\n".join(filepaths) self.log.debug("Created Simple Settings instance {}".format( instance.data From d294ad51748fa5ba02c6ca387f1c3af7ec1ba855 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Jul 2022 16:10:01 +0200 Subject: [PATCH 34/76] add also default values of missing attribute definitions --- openpype/pipeline/create/context.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index aecdb04635..a7a8eba383 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -29,6 +29,7 @@ UpdateData = collections.namedtuple("UpdateData", ["instance", "changes"]) class ImmutableKeyError(TypeError): """Accessed key is immutable so does not allow changes or removements.""" + def __init__(self, key, msg=None): self.immutable_key = key if not msg: @@ -40,6 +41,7 @@ class ImmutableKeyError(TypeError): class HostMissRequiredMethod(Exception): """Host does not have implemented required functions for creation.""" + def __init__(self, host, missing_methods): self.missing_methods = missing_methods self.host = host @@ -66,6 +68,7 @@ class InstanceMember: TODO: Implement and use! """ + def __init__(self, instance, name): self.instance = instance @@ -94,6 +97,7 @@ class AttributeValues: values(dict): Values after possible conversion. origin_data(dict): Values loaded from host before conversion. """ + def __init__(self, attr_defs, values, origin_data=None): from openpype.lib.attribute_definitions import UnknownDef @@ -174,6 +178,10 @@ class AttributeValues: output = {} for key in self._data: output[key] = self[key] + + for key, attr_def in self._attr_defs_by_key.items(): + if key not in output: + output[key] = attr_def.default return output @staticmethod @@ -196,6 +204,7 @@ class CreatorAttributeValues(AttributeValues): Args: instance (CreatedInstance): Instance for which are values hold. """ + def __init__(self, instance, *args, **kwargs): self.instance = instance super(CreatorAttributeValues, self).__init__(*args, **kwargs) @@ -211,6 +220,7 @@ class PublishAttributeValues(AttributeValues): publish_attributes(PublishAttributes): Wrapper for multiple publish attributes is used as parent object. """ + def __init__(self, publish_attributes, *args, **kwargs): self.publish_attributes = publish_attributes super(PublishAttributeValues, self).__init__(*args, **kwargs) @@ -232,6 +242,7 @@ class PublishAttributes: attr_plugins(list): List of publish plugins that may have defined attribute definitions. """ + def __init__(self, parent, origin_data, attr_plugins=None): self.parent = parent self._origin_data = copy.deepcopy(origin_data) @@ -270,6 +281,7 @@ class PublishAttributes: key(str): Plugin name. default: Default value if plugin was not found. """ + if key not in self._data: return default @@ -287,11 +299,13 @@ class PublishAttributes: def plugin_names_order(self): """Plugin names order by their 'order' attribute.""" + for name in self._plugin_names_order: yield name def data_to_store(self): """Convert attribute values to "data to store".""" + output = {} for key, attr_value in self._data.items(): output[key] = attr_value.data_to_store() @@ -299,6 +313,7 @@ class PublishAttributes: def changes(self): """Return changes per each key.""" + changes = {} for key, attr_val in self._data.items(): attr_changes = attr_val.changes() @@ -314,6 +329,7 @@ class PublishAttributes: def set_publish_plugins(self, attr_plugins): """Set publish plugins attribute definitions.""" + self._plugin_names_order = [] self._missing_plugins = [] self.attr_plugins = attr_plugins or [] @@ -365,6 +381,7 @@ class CreatedInstance: `openpype.pipeline.registered_host`. new(bool): Is instance new. """ + # Keys that can't be changed or removed from data after loading using # creator. # - 'creator_attributes' and 'publish_attributes' can change values of @@ -566,6 +583,7 @@ class CreatedInstance: @property def id(self): """Instance identifier.""" + return self._data["instance_id"] @property @@ -574,10 +592,12 @@ class CreatedInstance: Access to data is needed to modify values. """ + return self def changes(self): """Calculate and return changes.""" + changes = {} new_keys = set() for key, new_value in self._data.items(): From ba6afb8be5107affa0179a1ac4f7c64241ba35a3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Jul 2022 16:10:22 +0200 Subject: [PATCH 35/76] added jpeg extension to default settings --- openpype/settings/defaults/project_settings/traypublisher.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 5afaaee78c..d3e8028cdb 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -116,6 +116,7 @@ ".png", ".dpx", ".jpg", + ".jpeg", ".tiff", ".tif", ".mov", @@ -158,6 +159,7 @@ "extensions": [ ".exr", ".jpg", + ".jpeg", ".dpx", ".bmp", ".tif", From ac118ddefd8d2d0d576de43115d914e2e403f925 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Jul 2022 16:10:42 +0200 Subject: [PATCH 36/76] fill "source" in simple instances --- .../plugins/publish/collect_simple_instances.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index b2be43c701..bbd0221c88 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -1,4 +1,6 @@ import os + +import clique import pyblish.api @@ -29,6 +31,14 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): for filename in filepath_item["filenames"] ] + cols, rems = clique.assemble(filepaths) + source = None + if cols: + source = cols[0].format("{head}{padding}{tail}") + elif rems: + source = rems[0] + + instance.data["source"] = source instance.data["sourceFilepaths"] = filepaths instance.data["stagingDir"] = filepath_item["directory"] From 5592b4fb83ad0f3f4b5e222ed0f8361d0ebee346 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Jul 2022 16:10:59 +0200 Subject: [PATCH 37/76] fill representation in instance data update --- openpype/plugins/publish/collect_from_create_context.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/collect_from_create_context.py b/openpype/plugins/publish/collect_from_create_context.py index f6ead98809..d2be633cbe 100644 --- a/openpype/plugins/publish/collect_from_create_context.py +++ b/openpype/plugins/publish/collect_from_create_context.py @@ -47,12 +47,11 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin): "label": subset, "name": subset, "family": in_data["family"], - "families": instance_families + "families": instance_families, + "representations": [] }) for key, value in in_data.items(): if key not in instance.data: instance.data[key] = value self.log.info("collected instance: {}".format(instance.data)) self.log.info("parsing data: {}".format(in_data)) - - instance.data["representations"] = list() From be4ac5b56b9c815a268696ff23f072d14b029192 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 13 Jul 2022 17:25:51 +0200 Subject: [PATCH 38/76] add multiple items and review boolean to tray creator settings --- openpype/hosts/traypublisher/api/plugin.py | 7 +++++- .../project_settings/traypublisher.json | 24 ++++++++++++++++++- .../schema_project_traypublisher.json | 10 ++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 202664cfc6..a5d08c2967 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -1,3 +1,4 @@ +from openpype.lib.attribute_definitions import BoolDef from openpype.pipeline import ( Creator, CreatedInstance @@ -70,8 +71,10 @@ class SettingsCreator(TrayPublishCreator): folders=False, extensions=self.extensions, allow_sequences=self.allow_sequences, + single_item=not self.allow_multiple_items, label="Filepath", - ) + ), + BoolDef("allow_review", label="Reviewable", default=True) ] @classmethod @@ -92,6 +95,8 @@ class SettingsCreator(TrayPublishCreator): "detailed_description": item_data["detailed_description"], "extensions": item_data["extensions"], "allow_sequences": item_data["allow_sequences"], + "allow_multiple_items": item_data["allow_multiple_items"], + "allow_review": item_data["allow_review"], "default_variants": item_data["default_variants"] } ) diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index d3e8028cdb..e59200a13b 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -10,7 +10,9 @@ ], "description": "Backup of a working scene", "detailed_description": "Workfiles are full scenes from any application that are directly edited by artists. They represent a state of work on a task at a given point and are usually not directly referenced into other scenes.", - "allow_sequences": true, + "allow_sequences": false, + "allow_multiple_items": false, + "allow_review": false, "extensions": [ ".ma", ".mb", @@ -44,6 +46,8 @@ "description": "Clean models", "detailed_description": "Models should only contain geometry data, without any extras like cameras, locators or bones.\n\nKeep in mind that models published from tray publisher are not validated for correctness. ", "allow_sequences": false, + "allow_multiple_items": true, + "allow_review": false, "extensions": [ ".ma", ".mb", @@ -68,6 +72,8 @@ "description": "Geometry Caches", "detailed_description": "Alembic or bgeo cache of animated data", "allow_sequences": true, + "allow_multiple_items": true, + "allow_review": false, "extensions": [ ".abc", ".bgeo", @@ -90,6 +96,8 @@ "description": "Footage Plates", "detailed_description": "Any type of image seqeuence coming from outside of the studio. Usually camera footage, but could also be animatics used for reference.", "allow_sequences": true, + "allow_multiple_items": true, + "allow_review": true, "extensions": [ ".exr", ".png", @@ -111,6 +119,8 @@ "description": "Rendered images or video", "detailed_description": "Sequence or single file renders", "allow_sequences": true, + "allow_multiple_items": true, + "allow_review": true, "extensions": [ ".exr", ".png", @@ -133,6 +143,8 @@ "description": "3d Camera", "detailed_description": "Ideally this should be only camera itself with baked animation, however, it can technically also include helper geometry.", "allow_sequences": false, + "allow_multiple_items": true, + "allow_review": false, "extensions": [ ".abc", ".ma", @@ -156,6 +168,8 @@ "description": "Single image", "detailed_description": "Any image data can be published as image family. References, textures, concept art, matte paints. This is a fallback 2d family for everything that doesn't fit more specific family.", "allow_sequences": false, + "allow_multiple_items": true, + "allow_review": true, "extensions": [ ".exr", ".jpg", @@ -178,6 +192,8 @@ "description": "Sparse volumetric data", "detailed_description": "Hierarchical data structure for the efficient storage and manipulation of sparse volumetric data discretized on three-dimensional grids", "allow_sequences": true, + "allow_multiple_items": true, + "allow_review": false, "extensions": [ ".vdb" ] @@ -195,6 +211,8 @@ "description": "Matchmoving script", "detailed_description": "Script exported from matchmoving application to be later processed into a tracked camera with additional data", "allow_sequences": false, + "allow_multiple_items": true, + "allow_review": false, "extensions": [] }, { @@ -206,6 +224,8 @@ "description": "CG rig file", "detailed_description": "CG rigged character or prop. Rig should be clean of any extra data and directly loadable into it's respective application\t", "allow_sequences": false, + "allow_multiple_items": false, + "allow_review": false, "extensions": [ ".ma", ".blend", @@ -224,6 +244,8 @@ "description": "Simple Unreal Engine texture", "detailed_description": "Texture files with Unreal Engine naming conventions", "allow_sequences": false, + "allow_multiple_items": true, + "allow_review": false, "extensions": [] } ] diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 55c1b7b7d7..08c95609c0 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -67,6 +67,16 @@ "label": "Allow sequences", "type": "boolean" }, + { + "key": "allow_multiple_items", + "label": "Allow multiple items", + "type": "boolean" + }, + { + "key": "allow_review", + "label": "Allow review", + "type": "boolean" + }, { "type": "list", "key": "extensions", From 654b0fb1eb8883815c8493ddf665d824d9830bdb Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 13 Jul 2022 17:26:07 +0200 Subject: [PATCH 39/76] delete obsolete create_review_family plugin --- .../plugins/publish/collect_review_family.py | 31 ------------------- 1 file changed, 31 deletions(-) delete mode 100644 openpype/hosts/traypublisher/plugins/publish/collect_review_family.py diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_review_family.py b/openpype/hosts/traypublisher/plugins/publish/collect_review_family.py deleted file mode 100644 index 965e251527..0000000000 --- a/openpype/hosts/traypublisher/plugins/publish/collect_review_family.py +++ /dev/null @@ -1,31 +0,0 @@ -import pyblish.api -from openpype.lib import BoolDef -from openpype.pipeline import OpenPypePyblishPluginMixin - - -class CollectReviewFamily( - pyblish.api.InstancePlugin, OpenPypePyblishPluginMixin -): - """Add review family.""" - - label = "Collect Review Family" - order = pyblish.api.CollectorOrder - 0.49 - - hosts = ["traypublisher"] - families = [ - "image", - "render", - "plate", - "review" - ] - - def process(self, instance): - values = self.get_attr_values_from_data(instance.data) - if values.get("add_review_family"): - instance.data["families"].append("review") - - @classmethod - def get_attribute_defs(cls): - return [ - BoolDef("add_review_family", label="Review", default=True) - ] From 540e94e72680d9899689b18592deb09267e9ac3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 13 Jul 2022 17:44:29 +0200 Subject: [PATCH 40/76] :bug: fix git submodules weirdness --- .../UE_5.0/Content/Python/__init__.py => .gitmodules | 3 ++- openpype/hosts/unreal/integration/UE_5.0/Content/__init__.py | 0 2 files changed, 2 insertions(+), 1 deletion(-) rename openpype/hosts/unreal/integration/UE_5.0/Content/Python/__init__.py => .gitmodules (79%) delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Content/__init__.py diff --git a/openpype/hosts/unreal/integration/UE_5.0/Content/Python/__init__.py b/.gitmodules similarity index 79% rename from openpype/hosts/unreal/integration/UE_5.0/Content/Python/__init__.py rename to .gitmodules index 1e3eb5e792..b515851c81 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/Content/Python/__init__.py +++ b/.gitmodules @@ -1,6 +1,7 @@ [submodule "vendor/powershell/BurntToast"] path = vendor/powershell/BurntToast url = https://github.com/Windos/BurntToast.git + [submodule "vendor/powershell/PSWriteColor"] path = vendor/powershell/PSWriteColor - url = https://github.com/EvotecIT/PSWriteColor.git \ No newline at end of file + url = https://github.com/EvotecIT/PSWriteColor.git diff --git a/openpype/hosts/unreal/integration/UE_5.0/Content/__init__.py b/openpype/hosts/unreal/integration/UE_5.0/Content/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 From 2d2bf24c454d828a732f12a1dc628e1bfb375595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 13 Jul 2022 18:06:03 +0200 Subject: [PATCH 41/76] :bug: init submodules first --- tools/create_env.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/create_env.ps1 b/tools/create_env.ps1 index b1337b5635..387bdf919c 100644 --- a/tools/create_env.ps1 +++ b/tools/create_env.ps1 @@ -28,9 +28,11 @@ $current_dir = Get-Location $script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent $openpype_root = (Get-Item $script_dir).parent.FullName +& git submodule update --init --recursive # Install PSWriteColor to support colorized output to terminal $env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell" + function Exit-WithCode($exitcode) { # Only exit this host process if it's a child of another PowerShell parent process... $parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId From a427648abd8cb24a6ba906d5eb1ef7eb8212f96b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Jul 2022 19:05:23 +0200 Subject: [PATCH 42/76] implemented internal drag and drop and disabled sorting --- .../widgets/attribute_defs/files_widget.py | 62 ++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/openpype/widgets/attribute_defs/files_widget.py b/openpype/widgets/attribute_defs/files_widget.py index 698a91a1a5..4652e12ab1 100644 --- a/openpype/widgets/attribute_defs/files_widget.py +++ b/openpype/widgets/attribute_defs/files_widget.py @@ -1,6 +1,7 @@ import os import collections import uuid +import json from Qt import QtWidgets, QtCore, QtGui @@ -245,6 +246,62 @@ class FilesModel(QtGui.QStandardItemModel): return item_id, item + def mimeData(self, indexes): + item_ids = [ + index.data(ITEM_ID_ROLE) + for index in indexes + ] + encoded_data = QtCore.QByteArray() + stream = QtCore.QDataStream(encoded_data, QtCore.QIODevice.WriteOnly) + stream.writeQString(json.dumps(item_ids)) + mime_data = super(FilesModel, self).mimeData(indexes) + mime_data.setData("files_widget/internal_move", encoded_data) + return mime_data + + def dropMimeData(self, mime_data, action, row, col, index): + internal_move_data = mime_data.data("files_widget/internal_move") + if isinstance(internal_move_data, QtCore.QByteArray): + # Raw data are already QByteArrat and we don't have to load them + encoded_data = internal_move_data + else: + encoded_data = QtCore.QByteArray.fromRawData(internal_move_data) + stream = QtCore.QDataStream(encoded_data, QtCore.QIODevice.ReadOnly) + text = stream.readQString() + try: + item_ids = json.loads(text) + except Exception: + return False + + # Find matching item after which will be items moved + # - store item before moved items are removed + root = self.invisibleRootItem() + if row >= 0: + src_item = self.item(row) + else: + src_item_id = index.data(ITEM_ID_ROLE) + src_item = self._items_by_id.get(src_item_id) + + # Take out items that should be moved + items = [] + for item_id in item_ids: + item = self._items_by_id.get(item_id) + if item: + self.takeRow(item.row()) + items.append(item) + + # Skip if there are not items that can be moved + if not items: + return False + + # Calculate row where items should be inserted + if src_item: + src_row = src_item.row() + else: + src_row = root.rowCount() + + root.insertRow(src_row, items) + return True + class FilesProxyModel(QtCore.QSortFilterProxyModel): def __init__(self, *args, **kwargs): @@ -428,6 +485,9 @@ class FilesView(QtWidgets.QListView): QtWidgets.QAbstractItemView.ExtendedSelection ) self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.setAcceptDrops(True) + self.setDragEnabled(True) + self.setDragDropMode(self.InternalMove) remove_btn = InViewButton(self) pix_enabled = paint_image_with_color( @@ -637,8 +697,6 @@ class FilesWidget(QtWidgets.QFrame): ) self._widgets_by_id[item_id] = widget - self._files_proxy_model.sort(0) - if not self._in_set_value: self.value_changed.emit() From 2fbc1aa00676d542b7765773f376f3eee677c348 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Jul 2022 19:06:03 +0200 Subject: [PATCH 43/76] make drop available --- openpype/widgets/attribute_defs/files_widget.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openpype/widgets/attribute_defs/files_widget.py b/openpype/widgets/attribute_defs/files_widget.py index 4652e12ab1..eeb8f2d6dc 100644 --- a/openpype/widgets/attribute_defs/files_widget.py +++ b/openpype/widgets/attribute_defs/files_widget.py @@ -805,8 +805,12 @@ class FilesWidget(QtWidgets.QFrame): event.accept() def dropEvent(self, event): + if self._multivalue: + return + mime_data = event.mimeData() - if not self._multivalue and mime_data.hasUrls(): + if mime_data.hasUrls(): + event.accept() filepaths = [] for url in mime_data.urls(): filepath = url.toLocalFile() @@ -817,7 +821,8 @@ class FilesWidget(QtWidgets.QFrame): filepaths = self._files_proxy_model.filter_valid_files(filepaths) if filepaths: self._add_filepaths(filepaths) - event.accept() + + super(FilesWidget, self).dropEvent(event) def _add_filepaths(self, filepaths): self._files_model.add_filepaths(filepaths) From 08a9613c660f06d1158b2c8d9a2ffc5303a92e23 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Jul 2022 19:32:44 +0200 Subject: [PATCH 44/76] fix label height in files widget --- .../widgets/attribute_defs/files_widget.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/openpype/widgets/attribute_defs/files_widget.py b/openpype/widgets/attribute_defs/files_widget.py index eeb8f2d6dc..0b3a81e903 100644 --- a/openpype/widgets/attribute_defs/files_widget.py +++ b/openpype/widgets/attribute_defs/files_widget.py @@ -62,6 +62,14 @@ class DropEmpty(QtWidgets.QWidget): widget.setAlignment(QtCore.Qt.AlignCenter) widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + update_size_timer = QtCore.QTimer() + update_size_timer.setInterval(10) + update_size_timer.setSingleShot(True) + + update_size_timer.timeout.connect(self._on_update_size_timer) + + self._update_size_timer = update_size_timer + self._single_item = single_item self._allow_sequences = allow_sequences self._allowed_extensions = set() @@ -130,7 +138,28 @@ class DropEmpty(QtWidgets.QWidget): ", ".join(sorted(self._allowed_extensions)) ) + if self._items_label_widget.text() == items_label: + return + self._items_label_widget.setText(items_label) + self._update_size_timer.start() + + def resizeEvent(self, event): + super(DropEmpty, self).resizeEvent(event) + self._update_size_timer.start() + + def _on_update_size_timer(self): + """Recalculate height of label with extensions. + + Dynamic QLabel with word wrap does not handle properly it's sizeHint + calculations on show. This way it is recalculated. It is good practice + to trigger this method with small offset using '_update_size_timer'. + """ + + width = self._items_label_widget.width() + height = self._items_label_widget.heightForWidth(width) + self._items_label_widget.setMinimumHeight(height) + self._items_label_widget.updateGeometry() def paintEvent(self, event): super(DropEmpty, self).paintEvent(event) @@ -613,6 +642,7 @@ class FilesWidget(QtWidgets.QFrame): files_view.context_menu_requested.connect( self._on_context_menu_requested ) + self._in_set_value = False self._single_item = single_item self._multivalue = False From 731aecd71c58153dc45586ff67bc362d37e9f281 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 13 Jul 2022 21:48:50 +0200 Subject: [PATCH 45/76] sort plugins separatelly and don't count on order from report --- .../publisher/publish_report_viewer/report_items.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/publish_report_viewer/report_items.py b/openpype/tools/publisher/publish_report_viewer/report_items.py index b47d14da25..8a01569723 100644 --- a/openpype/tools/publisher/publish_report_viewer/report_items.py +++ b/openpype/tools/publisher/publish_report_viewer/report_items.py @@ -83,10 +83,8 @@ class PublishReport: logs = [] plugins_items_by_id = {} - plugins_id_order = [] for plugin_data in data["plugins_data"]: item = PluginItem(plugin_data) - plugins_id_order.append(item.id) plugins_items_by_id[item.id] = item for instance_data_item in plugin_data["instances_data"]: instance_id = instance_data_item["id"] @@ -95,6 +93,14 @@ class PublishReport: copy.deepcopy(log_item_data), item.id, instance_id ) logs.append(log_item) + sorted_plugins = sorted( + plugins_items_by_id.values(), + key=lambda item: item.order + ) + plugins_id_order = [ + plugin_item.id + for plugin_item in sorted_plugins + ] logs_by_instance_id = collections.defaultdict(list) for log_item in logs: From 84068bdd50a2a5bd5547d969d5f2fb74068067a5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 11:32:29 +0200 Subject: [PATCH 46/76] added ability to zoom text in report --- .../publish_report_viewer/widgets.py | 67 ++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/publish_report_viewer/widgets.py b/openpype/tools/publisher/publish_report_viewer/widgets.py index fd226ea0e4..61eb814a56 100644 --- a/openpype/tools/publisher/publish_report_viewer/widgets.py +++ b/openpype/tools/publisher/publish_report_viewer/widgets.py @@ -1,3 +1,4 @@ +from math import ceil from Qt import QtWidgets, QtCore, QtGui from openpype.widgets.nice_checkbox import NiceCheckbox @@ -137,13 +138,75 @@ class PluginLoadReportWidget(QtWidgets.QWidget): self._model.set_report(report) +class ZoomPlainText(QtWidgets.QPlainTextEdit): + def __init__(self, *args, **kwargs): + super(ZoomPlainText, self).__init__(*args, **kwargs) + + anim_timer = QtCore.QTimer() + anim_timer.setInterval(20) + + anim_timer.timeout.connect(self._scaling_callback) + + self._anim_timer = anim_timer + self._zoom_enabled = False + self._scheduled_scalings = 0 + self._point_size = None + + def wheelEvent(self, event): + if not self._zoom_enabled: + super(ZoomPlainText, self).wheelEvent(event) + return + + degrees = float(event.delta()) / 8 + steps = int(ceil(degrees / 5)) + self._scheduled_scalings += steps + if (self._scheduled_scalings * steps < 0): + self._scheduled_scalings = steps + + self._anim_timer.start() + + def _scaling_callback(self): + if self._scheduled_scalings == 0: + self._anim_timer.stop() + return + + factor = 1.0 + (self._scheduled_scalings / 300) + font = self.font() + if self._point_size is None: + self._point_size = font.pointSizeF() + + self._point_size *= factor + if self._point_size < 1: + self._point_size = 1.0 + + font.setPointSizeF(self._point_size) + # Using 'self.setFont(font)' would not be propagated when stylesheets + # are applied on this widget + self.setStyleSheet("font-size: {}pt".format(font.pointSize())) + + if self._scheduled_scalings > 0: + self._scheduled_scalings -= 1 + else: + self._scheduled_scalings += 1 + + def keyPressEvent(self, event): + if event.key() == QtCore.Qt.Key_Control: + self._zoom_enabled = True + super(ZoomPlainText, self).keyPressEvent(event) + + def keyReleaseEvent(self, event): + if event.key() == QtCore.Qt.Key_Control: + self._zoom_enabled = False + super(ZoomPlainText, self).keyReleaseEvent(event) + + class DetailsWidget(QtWidgets.QWidget): def __init__(self, parent): super(DetailsWidget, self).__init__(parent) - output_widget = QtWidgets.QPlainTextEdit(self) - output_widget.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + output_widget = ZoomPlainText(self) output_widget.setObjectName("PublishLogConsole") + output_widget.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) From c16a2d6ed53caa68d44b986c1aec375fda8e34f0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 12:17:19 +0200 Subject: [PATCH 47/76] moved collect cleanup keys earlier --- openpype/plugins/publish/collect_cleanup_keys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_cleanup_keys.py b/openpype/plugins/publish/collect_cleanup_keys.py index 635b038387..b9cd1a9fc9 100644 --- a/openpype/plugins/publish/collect_cleanup_keys.py +++ b/openpype/plugins/publish/collect_cleanup_keys.py @@ -14,7 +14,7 @@ class CollectCleanupKeys(pyblish.api.ContextPlugin): """Prepare keys for 'ExplicitCleanUp' plugin.""" label = "Collect Cleanup Keys" - order = pyblish.api.CollectorOrder + order = pyblish.api.CollectorOrder - 0.5 def process(self, context): context.data["cleanupFullPaths"] = [] From 43d744b24b54f21ca2f7e622ca491af91833e689 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 12:17:43 +0200 Subject: [PATCH 48/76] instance data is filled with instance asset specific values if are not already available on instance --- .../publish/collect_anatomy_instance_data.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openpype/plugins/publish/collect_anatomy_instance_data.py b/openpype/plugins/publish/collect_anatomy_instance_data.py index c75534cf83..f67d3373d9 100644 --- a/openpype/plugins/publish/collect_anatomy_instance_data.py +++ b/openpype/plugins/publish/collect_anatomy_instance_data.py @@ -51,6 +51,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): project_name = legacy_io.active_project() self.fill_missing_asset_docs(context, project_name) + self.fill_instance_data_from_asset(context) self.fill_latest_versions(context, project_name) self.fill_anatomy_data(context) @@ -115,6 +116,23 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): "Not found asset documents with names \"{}\"." ).format(joined_asset_names)) + def fill_instance_data_from_asset(self, context): + for instance in context: + asset_doc = instance.data.get("assetEntity") + if not asset_doc: + continue + + asset_data = asset_doc["data"] + for key in ( + "fps", + "frameStart", + "frameEnd", + "handleStart", + "handleEnd", + ): + if key not in instance.data and key in asset_data: + instance.data[key] = asset_data[key] + def fill_latest_versions(self, context, project_name): """Try to find latest version for each instance's subset. From 4f133d26b4312bcba7b3b2ddbcac19131667d590 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 12:29:13 +0200 Subject: [PATCH 49/76] changed allow_review to reviewable which affect default value of reviewable on instances --- openpype/hosts/traypublisher/api/plugin.py | 8 +++++-- .../project_settings/traypublisher.json | 22 +++++++++---------- .../schema_project_traypublisher.json | 4 ++-- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 3877d33055..6966935091 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -87,7 +87,11 @@ class SettingsCreator(TrayPublishCreator): single_item=not self.allow_multiple_items, label="Filepath", ), - BoolDef("allow_review", label="Reviewable", default=True) + BoolDef( + "reviewable", + label="Reviewable", + default=self.reviewable + ) ] @classmethod @@ -109,7 +113,7 @@ class SettingsCreator(TrayPublishCreator): "extensions": item_data["extensions"], "allow_sequences": item_data["allow_sequences"], "allow_multiple_items": item_data["allow_multiple_items"], - "allow_review": item_data["allow_review"], + "reviewable": item_data["reviewable"], "default_variants": item_data["default_variants"] } ) diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index e59200a13b..619d54dbaf 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -12,7 +12,7 @@ "detailed_description": "Workfiles are full scenes from any application that are directly edited by artists. They represent a state of work on a task at a given point and are usually not directly referenced into other scenes.", "allow_sequences": false, "allow_multiple_items": false, - "allow_review": false, + "reviewable": false, "extensions": [ ".ma", ".mb", @@ -47,7 +47,7 @@ "detailed_description": "Models should only contain geometry data, without any extras like cameras, locators or bones.\n\nKeep in mind that models published from tray publisher are not validated for correctness. ", "allow_sequences": false, "allow_multiple_items": true, - "allow_review": false, + "reviewable": false, "extensions": [ ".ma", ".mb", @@ -73,7 +73,7 @@ "detailed_description": "Alembic or bgeo cache of animated data", "allow_sequences": true, "allow_multiple_items": true, - "allow_review": false, + "reviewable": false, "extensions": [ ".abc", ".bgeo", @@ -97,7 +97,7 @@ "detailed_description": "Any type of image seqeuence coming from outside of the studio. Usually camera footage, but could also be animatics used for reference.", "allow_sequences": true, "allow_multiple_items": true, - "allow_review": true, + "reviewable": true, "extensions": [ ".exr", ".png", @@ -120,7 +120,7 @@ "detailed_description": "Sequence or single file renders", "allow_sequences": true, "allow_multiple_items": true, - "allow_review": true, + "reviewable": true, "extensions": [ ".exr", ".png", @@ -144,7 +144,7 @@ "detailed_description": "Ideally this should be only camera itself with baked animation, however, it can technically also include helper geometry.", "allow_sequences": false, "allow_multiple_items": true, - "allow_review": false, + "reviewable": false, "extensions": [ ".abc", ".ma", @@ -169,7 +169,7 @@ "detailed_description": "Any image data can be published as image family. References, textures, concept art, matte paints. This is a fallback 2d family for everything that doesn't fit more specific family.", "allow_sequences": false, "allow_multiple_items": true, - "allow_review": true, + "reviewable": true, "extensions": [ ".exr", ".jpg", @@ -193,7 +193,7 @@ "detailed_description": "Hierarchical data structure for the efficient storage and manipulation of sparse volumetric data discretized on three-dimensional grids", "allow_sequences": true, "allow_multiple_items": true, - "allow_review": false, + "reviewable": false, "extensions": [ ".vdb" ] @@ -212,7 +212,7 @@ "detailed_description": "Script exported from matchmoving application to be later processed into a tracked camera with additional data", "allow_sequences": false, "allow_multiple_items": true, - "allow_review": false, + "reviewable": false, "extensions": [] }, { @@ -225,7 +225,7 @@ "detailed_description": "CG rigged character or prop. Rig should be clean of any extra data and directly loadable into it's respective application\t", "allow_sequences": false, "allow_multiple_items": false, - "allow_review": false, + "reviewable": false, "extensions": [ ".ma", ".blend", @@ -245,7 +245,7 @@ "detailed_description": "Texture files with Unreal Engine naming conventions", "allow_sequences": false, "allow_multiple_items": true, - "allow_review": false, + "reviewable": false, "extensions": [] } ] diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 08c95609c0..269b47459c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -73,8 +73,8 @@ "type": "boolean" }, { - "key": "allow_review", - "label": "Allow review", + "key": "reviewable", + "label": "Reviewable", "type": "boolean" }, { From 1eceb7296df14e914a3c1de2c76dd1dcb84ab6ad Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 12:29:43 +0200 Subject: [PATCH 50/76] define some extensions for which reviewable could work --- .../publish/collect_simple_instances.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index f76306cf05..4992d0a8be 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -12,6 +12,28 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): hosts = ["traypublisher"] + _image_extensions = [ + ".ani", ".anim", ".apng", ".art", ".bmp", ".bpg", ".bsave", ".cal", + ".cin", ".cpc", ".cpt", ".dds", ".dpx", ".ecw", ".exr", ".fits", + ".flic", ".flif", ".fpx", ".gif", ".hdri", ".hevc", ".icer", + ".icns", ".ico", ".cur", ".ics", ".ilbm", ".jbig", ".jbig2", + ".jng", ".jpeg", ".jpeg-ls", ".jpeg", ".2000", ".jpg", ".xr", + ".jpeg", ".xt", ".jpeg-hdr", ".kra", ".mng", ".miff", ".nrrd", + ".ora", ".pam", ".pbm", ".pgm", ".ppm", ".pnm", ".pcx", ".pgf", + ".pictor", ".png", ".psb", ".psp", ".qtvr", ".ras", + ".rgbe", ".logluv", ".tiff", ".sgi", ".tga", ".tiff", ".tiff/ep", + ".tiff/it", ".ufo", ".ufp", ".wbmp", ".webp", ".xbm", ".xcf", + ".xpm", ".xwd" + ] + _video_extensions = [ + ".3g2", ".3gp", ".amv", ".asf", ".avi", ".drc", ".f4a", ".f4b", + ".f4p", ".f4v", ".flv", ".gif", ".gifv", ".m2v", ".m4p", ".m4v", + ".mkv", ".mng", ".mov", ".mp2", ".mp4", ".mpe", ".mpeg", ".mpg", + ".mpv", ".mxf", ".nsv", ".ogg", ".ogv", ".qt", ".rm", ".rmvb", + ".roq", ".svi", ".vob", ".webm", ".wmv", ".yuv" + ] + _review_extensions = _image_extensions + _video_extensions + def process(self, instance): if not instance.data.get("settings_creator"): return From a2a83623ff8f3f9ddece29227b60428721e5b56c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 12:30:40 +0200 Subject: [PATCH 51/76] instance staging dir lead to temp --- .../plugins/publish/collect_simple_instances.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index 4992d0a8be..6a583b2e50 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -38,11 +38,15 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): if not instance.data.get("settings_creator"): return - if "families" not in instance.data: - instance.data["families"] = [] + # Create instance's staging dir in temp + tmp_folder = tempfile.mkdtemp(prefix="traypublisher_") + instance.data["stagingDir"] = tmp_folder + instance.context.data["cleanupFullPaths"].append(tmp_folder) + + self.log.debug( + "Created temp staging directory for instance {}".format(tmp_folder) + ) - if "representations" not in instance.data: - instance.data["representations"] = [] repres = instance.data["representations"] creator_attributes = instance.data["creator_attributes"] @@ -62,7 +66,6 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): instance.data["source"] = source instance.data["sourceFilepaths"] = filepaths - instance.data["stagingDir"] = filepath_item["directory"] filenames = filepath_item["filenames"] _, ext = os.path.splitext(filenames[0]) From a088db2db9db034c752d7dd299614c3a8e739357 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 12:46:57 +0200 Subject: [PATCH 52/76] modified validator if not existing paths as there is a chance that filepaths are not filled at all --- .../plugins/publish/validate_filepaths.py | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_filepaths.py b/openpype/hosts/traypublisher/plugins/publish/validate_filepaths.py index c7302b1005..e02116e10b 100644 --- a/openpype/hosts/traypublisher/plugins/publish/validate_filepaths.py +++ b/openpype/hosts/traypublisher/plugins/publish/validate_filepaths.py @@ -3,8 +3,17 @@ import pyblish.api from openpype.pipeline import PublishValidationError -class ValidateWorkfilePath(pyblish.api.InstancePlugin): - """Validate existence of workfile instance existence.""" +class ValidateFilePath(pyblish.api.InstancePlugin): + """Validate existence of source filepaths on instance. + + Plugins looks into key 'sourceFilepaths' and validate if paths there + actually exist on disk. + + Also validate if the key is filled but is empty. In that case also + crashes so do not fill the key if unfilled value should not cause error. + + This is primarily created for Simple Creator instances. + """ label = "Validate Workfile" order = pyblish.api.ValidatorOrder - 0.49 @@ -14,12 +23,24 @@ class ValidateWorkfilePath(pyblish.api.InstancePlugin): def process(self, instance): if "sourceFilepaths" not in instance.data: self.log.info(( - "Can't validate source filepaths existence." + "Skipped validation of source filepaths existence." " Instance does not have collected 'sourceFilepaths'" )) return - filepaths = instance.data.get("sourceFilepaths") + filepaths = instance.data["sourceFilepaths"] + if not filepaths: + raise PublishValidationError( + ( + "Source filepaths of '{}' instance \"{}\" are not filled" + ).format(instance.data["family"], instance.data["name"]), + "File not filled", + ( + "## Files were not filled" + "\nThis could mean that you didn't enter files into file" + "input." + ) + ) not_found_files = [ filepath From bc09a92d52be3782015337c436f89f9ef832a3cd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 12:47:22 +0200 Subject: [PATCH 53/76] modified simple instance collector to be able handle multivalue of fileitems --- .../publish/collect_simple_instances.py | 121 ++++++++++++++---- 1 file changed, 93 insertions(+), 28 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index 6a583b2e50..1b2129f48e 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -1,4 +1,6 @@ import os +import json +import tempfile import clique import pyblish.api @@ -38,50 +40,113 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): if not instance.data.get("settings_creator"): return + instance_label = instance.data["name"] # Create instance's staging dir in temp tmp_folder = tempfile.mkdtemp(prefix="traypublisher_") instance.data["stagingDir"] = tmp_folder instance.context.data["cleanupFullPaths"].append(tmp_folder) - self.log.debug( - "Created temp staging directory for instance {}".format(tmp_folder) - ) + self.log.debug(( + "Created temp staging directory for instance {}. {}" + ).format(instance_label, tmp_folder)) repres = instance.data["representations"] creator_attributes = instance.data["creator_attributes"] - filepath_item = creator_attributes["filepath"] - self.log.info(filepath_item) - filepaths = [ - os.path.join(filepath_item["directory"], filename) - for filename in filepath_item["filenames"] - ] + self.log.info(json.dumps(creator_attributes)) + filepath_items = creator_attributes["filepath"] + if not isinstance(filepath_items, list): + filepath_items = [filepath_items] - cols, rems = clique.assemble(filepaths) + # Last found representation is used as source for instance source = None + # Check if review is enabled and should be created + reviewable = creator_attributes.get("reviewable") + # Store review representation - first found that can be used for + # review is stored + review_representation = None + review_path = None + + # Make sure there are no representations with same name + repre_names_counter = {} + # Store created names for logging + _repre_names = [] + # Store filepaths for validation of their existence + source_filepaths = [] + + # Create representations + for filepath_item in filepath_items: + filepaths = [ + os.path.join(filepath_item["directory"], filename) + for filename in filepath_item["filenames"] + ] + source_filepaths.extend(filepaths) + + source = self._calculate_source(filepaths) + filenames = filepath_item["filenames"] + _, ext = os.path.splitext(filenames[0]) + if len(filenames) == 1: + filenames = filenames[0] + + repre_name = repre_ext = ext[1:] + if repre_name not in repre_names_counter: + repre_names_counter[repre_name] = 2 + else: + counter = repre_names_counter[repre_name] + repre_names_counter[repre_name] += 1 + repre_name = "{}_{}".format(repre_name, counter) + + _repre_names.append('"{}"'.format(repre_name)) + representation = { + "ext": repre_ext, + "name": repre_name, + "stagingDir": filepath_item["directory"], + "files": filenames, + "tags": [] + } + repres.append(representation) + + if ( + reviewable + and review_representation is None + and ext in self._review_extensions + ): + review_representation = representation + review_path = source + + instance.data["source"] = source + instance.data["sourceFilepaths"] = source_filepaths + + if reviewable: + self._prepare_review(instance, review_representation, review_path) + + self.log.debug(( + "Created Simple Settings instance \"{}\"" + " with {} representations: {}" + ).format(instance_label, len(repres), ", ".join(_repre_names))) + + def _calculate_source(self, filepaths): + if not filepaths: + return None + cols, rems = clique.assemble(filepaths) if cols: source = cols[0].format("{head}{padding}{tail}") elif rems: source = rems[0] + return source - instance.data["source"] = source - instance.data["sourceFilepaths"] = filepaths + def _prepare_review(self, instance, review_representation, review_path): + if not review_representation: + self.log.waring(( + "Didn't find any representation" + " that could be used as source for review" + )) + return - filenames = filepath_item["filenames"] - _, ext = os.path.splitext(filenames[0]) - ext = ext[1:] - if len(filenames) == 1: - filenames = filenames[0] + if "review" not in instance.data["families"]: + instance.data["families"].append("review") - repres.append({ - "ext": ext, - "name": ext, - "stagingDir": filepath_item["directory"], - "files": filenames - }) - - instance.data["source"] = "\n".join(filepaths) - - self.log.debug("Created Simple Settings instance {}".format( - instance.data + review_representation["tags"].append("review") + self.log.debug("Representation {} was marked for review. {}".format( + review_representation["name"], review_path )) From b51c44d6f203a9cf0958bbc83504e4202ec1b98f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 12:52:35 +0200 Subject: [PATCH 54/76] modified error message --- .../plugins/publish/validate_filepaths.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_filepaths.py b/openpype/hosts/traypublisher/plugins/publish/validate_filepaths.py index e02116e10b..749199fbd3 100644 --- a/openpype/hosts/traypublisher/plugins/publish/validate_filepaths.py +++ b/openpype/hosts/traypublisher/plugins/publish/validate_filepaths.py @@ -28,18 +28,22 @@ class ValidateFilePath(pyblish.api.InstancePlugin): )) return + family = instance.data["family"] + label = instance.data["name"] filepaths = instance.data["sourceFilepaths"] if not filepaths: raise PublishValidationError( ( "Source filepaths of '{}' instance \"{}\" are not filled" - ).format(instance.data["family"], instance.data["name"]), + ).format(family, label), "File not filled", ( "## Files were not filled" - "\nThis could mean that you didn't enter files into file" - "input." - ) + "\nThis mean that you didn't enter any files into required" + " file input." + "\n- Please refresh publishing and check instance" + " {}" + ).format(label) ) not_found_files = [ @@ -55,11 +59,7 @@ class ValidateFilePath(pyblish.api.InstancePlugin): raise PublishValidationError( ( "Filepath of '{}' instance \"{}\" does not exist:\n{}" - ).format( - instance.data["family"], - instance.data["name"], - joined_paths - ), + ).format(family, label, joined_paths), "File not found", ( "## Files were not found\nFiles\n{}" From 465c506162cba6b55981f56fba382b38c79ffc11 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 12:52:41 +0200 Subject: [PATCH 55/76] fix typo --- .../traypublisher/plugins/publish/collect_simple_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index 1b2129f48e..8dd2964252 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -137,7 +137,7 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): def _prepare_review(self, instance, review_representation, review_path): if not review_representation: - self.log.waring(( + self.log.warning(( "Didn't find any representation" " that could be used as source for review" )) From 9c1dd8b5ca4df655bf533ac23773926caee1afd6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 12:59:45 +0200 Subject: [PATCH 56/76] save changes before reset --- openpype/tools/publisher/control.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index f692bb4000..b48bb61386 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -575,6 +575,8 @@ class PublisherController: # Stop publishing self.stop_publish() + self.save_changes() + # Reset avalon context self.create_context.reset_avalon_context() From 36ae9ff49cff8a460bcd46ef49e7c43c440874a7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 13:38:40 +0200 Subject: [PATCH 57/76] added some docstring to plugin --- .../plugins/publish/collect_simple_instances.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index 8dd2964252..424cf7d88d 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -7,7 +7,21 @@ import pyblish.api class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): - """Collect data for instances created by settings creators.""" + """Collect data for instances created by settings creators. + + Plugin create representations based on 'filepath' attribute stored + on instance. + + Representations can be marked for review and in that case is also added + 'review' family to instance families. For review can be marked only one + representation so **first** representation that has extension available + in '_review_extensions' is used for review. + + For 'source' on instance is used path from last created representation. + + Set staging directory on instance. That is probably never used because + each created representation has it's own staging dir. + """ label = "Collect Settings Simple Instances" order = pyblish.api.CollectorOrder - 0.49 From 8083c1ed0aea6b0d9185d86c18d4b2e3c552ea50 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 13:42:30 +0200 Subject: [PATCH 58/76] remove not relevant lines --- .../traypublisher/plugins/publish/collect_simple_instances.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index 424cf7d88d..e8e1c1013c 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -140,8 +140,6 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): ).format(instance_label, len(repres), ", ".join(_repre_names))) def _calculate_source(self, filepaths): - if not filepaths: - return None cols, rems = clique.assemble(filepaths) if cols: source = cols[0].format("{head}{padding}{tail}") From b5fa8b524e6fa13669e8f755c97d75f3ea217158 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 13:51:20 +0200 Subject: [PATCH 59/76] unify imports --- openpype/hosts/traypublisher/api/plugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 6966935091..46fb4fdb51 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -1,9 +1,8 @@ -from openpype.lib.attribute_definitions import BoolDef +from openpype.lib.attribute_definitions import BoolDef, FileDef from openpype.pipeline import ( Creator, CreatedInstance ) -from openpype.lib import FileDef from .pipeline import ( list_instances, From 7729d53921659abe7751cd007cda78156681afc1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 14:34:50 +0200 Subject: [PATCH 60/76] files item can have custom extensions label --- openpype/lib/attribute_definitions.py | 13 +++++++--- .../widgets/attribute_defs/files_widget.py | 26 ++++++++++++++----- openpype/widgets/attribute_defs/widgets.py | 5 +++- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index a1f7c1e0f4..17658eef93 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -14,6 +14,7 @@ class AbstractAttrDefMeta(ABCMeta): Each object of `AbtractAttrDef` mus have defined 'key' attribute. """ + def __call__(self, *args, **kwargs): obj = super(AbstractAttrDefMeta, self).__call__(*args, **kwargs) init_class = getattr(obj, "__init__class__", None) @@ -45,6 +46,7 @@ class AbtractAttrDef: is_label_horizontal(bool): UI specific argument. Specify if label is next to value input or ahead. """ + is_value_def = True def __init__( @@ -77,6 +79,7 @@ class AbtractAttrDef: Convert passed value to a valid type. Use default if value can't be converted. """ + pass @@ -113,6 +116,7 @@ class UnknownDef(AbtractAttrDef): This attribute can be used to keep existing data unchanged but does not have known definition of type. """ + def __init__(self, key, default=None, **kwargs): kwargs["default"] = default super(UnknownDef, self).__init__(key, **kwargs) @@ -204,6 +208,7 @@ class TextDef(AbtractAttrDef): placeholder(str): UI placeholder for attribute. default(str, None): Default value. Empty string used when not defined. """ + def __init__( self, key, multiline=None, regex=None, placeholder=None, default=None, **kwargs @@ -531,14 +536,15 @@ class FileDef(AbtractAttrDef): Args: single_item(bool): Allow only single path item. folders(bool): Allow folder paths. - extensions(list): Allow files with extensions. Empty list will + extensions(List[str]): Allow files with extensions. Empty list will allow all extensions and None will disable files completely. - default(str, list): Defautl value. + extensions_label(str): Custom label shown instead of extensions in UI. + default(str, List[str]): Default value. """ def __init__( self, key, single_item=True, folders=None, extensions=None, - allow_sequences=True, default=None, **kwargs + allow_sequences=True, extensions_label=None, default=None, **kwargs ): if folders is None and extensions is None: folders = True @@ -578,6 +584,7 @@ class FileDef(AbtractAttrDef): self.folders = folders self.extensions = set(extensions) self.allow_sequences = allow_sequences + self.extensions_label = extensions_label super(FileDef, self).__init__(key, default=default, **kwargs) def __eq__(self, other): diff --git a/openpype/widgets/attribute_defs/files_widget.py b/openpype/widgets/attribute_defs/files_widget.py index 0b3a81e903..508da4893b 100644 --- a/openpype/widgets/attribute_defs/files_widget.py +++ b/openpype/widgets/attribute_defs/files_widget.py @@ -34,7 +34,7 @@ class SupportLabel(QtWidgets.QLabel): class DropEmpty(QtWidgets.QWidget): _empty_extensions = "Any file" - def __init__(self, single_item, allow_sequences, parent): + def __init__(self, single_item, allow_sequences, extensions_label, parent): super(DropEmpty, self).__init__(parent) drop_label_widget = QtWidgets.QLabel("Drag & Drop files here", self) @@ -70,7 +70,11 @@ class DropEmpty(QtWidgets.QWidget): self._update_size_timer = update_size_timer + if extensions_label and not extensions_label.startswith(" "): + extensions_label = " " + extensions_label + self._single_item = single_item + self._extensions_label = extensions_label self._allow_sequences = allow_sequences self._allowed_extensions = set() self._allow_folders = None @@ -123,24 +127,32 @@ class DropEmpty(QtWidgets.QWidget): items_label = "Single " if len(allowed_items) == 1: - allowed_items_label = allowed_items[0] + extensions_label = allowed_items[0] elif len(allowed_items) == 2: - allowed_items_label = " or ".join(allowed_items) + extensions_label = " or ".join(allowed_items) else: last_item = allowed_items.pop(-1) new_last_item = " or ".join(last_item, allowed_items.pop(-1)) allowed_items.append(new_last_item) - allowed_items_label = ", ".join(allowed_items) + extensions_label = ", ".join(allowed_items) + + allowed_items_label = extensions_label items_label += allowed_items_label + label_tooltip = None if self._allowed_extensions: items_label += " of\n{}".format( ", ".join(sorted(self._allowed_extensions)) ) + if self._extensions_label: + label_tooltip = items_label + items_label = self._extensions_label + if self._items_label_widget.text() == items_label: return + self._items_label_widget.setToolTip(label_tooltip) self._items_label_widget.setText(items_label) self._update_size_timer.start() @@ -618,11 +630,13 @@ class FilesView(QtWidgets.QListView): class FilesWidget(QtWidgets.QFrame): value_changed = QtCore.Signal() - def __init__(self, single_item, allow_sequences, parent): + def __init__(self, single_item, allow_sequences, extensions_label, parent): super(FilesWidget, self).__init__(parent) self.setAcceptDrops(True) - empty_widget = DropEmpty(single_item, allow_sequences, self) + empty_widget = DropEmpty( + single_item, allow_sequences, extensions_label, self + ) files_model = FilesModel(single_item, allow_sequences) files_proxy_model = FilesProxyModel() diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py index b6493b80a8..975b2df955 100644 --- a/openpype/widgets/attribute_defs/widgets.py +++ b/openpype/widgets/attribute_defs/widgets.py @@ -443,7 +443,10 @@ class UnknownAttrWidget(_BaseAttrDefWidget): class FileAttrWidget(_BaseAttrDefWidget): def _ui_init(self): input_widget = FilesWidget( - self.attr_def.single_item, self.attr_def.allow_sequences, self + self.attr_def.single_item, + self.attr_def.allow_sequences, + self.attr_def.extensions_label, + self ) if self.attr_def.tooltip: From 1baf0457baac213c2f443d02c217bcedfb5870f5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 14:35:31 +0200 Subject: [PATCH 61/76] added second file input for reviewables --- openpype/hosts/traypublisher/api/plugin.py | 38 ++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 46fb4fdb51..cb02a5600e 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -1,4 +1,4 @@ -from openpype.lib.attribute_definitions import BoolDef, FileDef +from openpype.lib.attribute_definitions import FileDef from openpype.pipeline import ( Creator, CreatedInstance @@ -12,6 +12,29 @@ from .pipeline import ( ) +IMAGE_EXTENSIONS = [ + ".ani", ".anim", ".apng", ".art", ".bmp", ".bpg", ".bsave", ".cal", + ".cin", ".cpc", ".cpt", ".dds", ".dpx", ".ecw", ".exr", ".fits", + ".flic", ".flif", ".fpx", ".gif", ".hdri", ".hevc", ".icer", + ".icns", ".ico", ".cur", ".ics", ".ilbm", ".jbig", ".jbig2", + ".jng", ".jpeg", ".jpeg-ls", ".jpeg", ".2000", ".jpg", ".xr", + ".jpeg", ".xt", ".jpeg-hdr", ".kra", ".mng", ".miff", ".nrrd", + ".ora", ".pam", ".pbm", ".pgm", ".ppm", ".pnm", ".pcx", ".pgf", + ".pictor", ".png", ".psb", ".psp", ".qtvr", ".ras", + ".rgbe", ".logluv", ".tiff", ".sgi", ".tga", ".tiff", ".tiff/ep", + ".tiff/it", ".ufo", ".ufp", ".wbmp", ".webp", ".xbm", ".xcf", + ".xpm", ".xwd" +] +VIDEO_EXTENSIONS = [ + ".3g2", ".3gp", ".amv", ".asf", ".avi", ".drc", ".f4a", ".f4b", + ".f4p", ".f4v", ".flv", ".gif", ".gifv", ".m2v", ".m4p", ".m4v", + ".mkv", ".mng", ".mov", ".mp2", ".mp4", ".mpe", ".mpeg", ".mpg", + ".mpv", ".mxf", ".nsv", ".ogg", ".ogv", ".qt", ".rm", ".rmvb", + ".roq", ".svi", ".vob", ".webm", ".wmv", ".yuv" +] +REVIEW_EXTENSIONS = IMAGE_EXTENSIONS + VIDEO_EXTENSIONS + + class TrayPublishCreator(Creator): create_allow_context_change = True host_name = "traypublisher" @@ -84,12 +107,16 @@ class SettingsCreator(TrayPublishCreator): extensions=self.extensions, allow_sequences=self.allow_sequences, single_item=not self.allow_multiple_items, - label="Filepath", + label="Representations", ), - BoolDef( + FileDef( "reviewable", - label="Reviewable", - default=self.reviewable + folders=False, + extensions=REVIEW_EXTENSIONS, + allow_sequences=True, + single_item=True, + label="Reviewable representations", + extensions_label="Single reviewable item" ) ] @@ -112,7 +139,6 @@ class SettingsCreator(TrayPublishCreator): "extensions": item_data["extensions"], "allow_sequences": item_data["allow_sequences"], "allow_multiple_items": item_data["allow_multiple_items"], - "reviewable": item_data["reviewable"], "default_variants": item_data["default_variants"] } ) From 8a6ee91ec2251939a9e9c8f5624765ad2c37b826 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 14:36:00 +0200 Subject: [PATCH 62/76] removed reviewable key from settings (unused) --- .../defaults/project_settings/traypublisher.json | 15 +-------------- .../schema_project_traypublisher.json | 5 ----- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 619d54dbaf..80c4a6bed1 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -12,7 +12,6 @@ "detailed_description": "Workfiles are full scenes from any application that are directly edited by artists. They represent a state of work on a task at a given point and are usually not directly referenced into other scenes.", "allow_sequences": false, "allow_multiple_items": false, - "reviewable": false, "extensions": [ ".ma", ".mb", @@ -47,7 +46,6 @@ "detailed_description": "Models should only contain geometry data, without any extras like cameras, locators or bones.\n\nKeep in mind that models published from tray publisher are not validated for correctness. ", "allow_sequences": false, "allow_multiple_items": true, - "reviewable": false, "extensions": [ ".ma", ".mb", @@ -73,7 +71,6 @@ "detailed_description": "Alembic or bgeo cache of animated data", "allow_sequences": true, "allow_multiple_items": true, - "reviewable": false, "extensions": [ ".abc", ".bgeo", @@ -97,7 +94,6 @@ "detailed_description": "Any type of image seqeuence coming from outside of the studio. Usually camera footage, but could also be animatics used for reference.", "allow_sequences": true, "allow_multiple_items": true, - "reviewable": true, "extensions": [ ".exr", ".png", @@ -120,7 +116,6 @@ "detailed_description": "Sequence or single file renders", "allow_sequences": true, "allow_multiple_items": true, - "reviewable": true, "extensions": [ ".exr", ".png", @@ -144,7 +139,6 @@ "detailed_description": "Ideally this should be only camera itself with baked animation, however, it can technically also include helper geometry.", "allow_sequences": false, "allow_multiple_items": true, - "reviewable": false, "extensions": [ ".abc", ".ma", @@ -169,7 +163,6 @@ "detailed_description": "Any image data can be published as image family. References, textures, concept art, matte paints. This is a fallback 2d family for everything that doesn't fit more specific family.", "allow_sequences": false, "allow_multiple_items": true, - "reviewable": true, "extensions": [ ".exr", ".jpg", @@ -193,7 +186,6 @@ "detailed_description": "Hierarchical data structure for the efficient storage and manipulation of sparse volumetric data discretized on three-dimensional grids", "allow_sequences": true, "allow_multiple_items": true, - "reviewable": false, "extensions": [ ".vdb" ] @@ -212,7 +204,6 @@ "detailed_description": "Script exported from matchmoving application to be later processed into a tracked camera with additional data", "allow_sequences": false, "allow_multiple_items": true, - "reviewable": false, "extensions": [] }, { @@ -225,7 +216,6 @@ "detailed_description": "CG rigged character or prop. Rig should be clean of any extra data and directly loadable into it's respective application\t", "allow_sequences": false, "allow_multiple_items": false, - "reviewable": false, "extensions": [ ".ma", ".blend", @@ -238,14 +228,11 @@ "identifier": "", "label": "Simple UE texture", "icon": "fa.image", - "default_variants": [ - "" - ], + "default_variants": [], "description": "Simple Unreal Engine texture", "detailed_description": "Texture files with Unreal Engine naming conventions", "allow_sequences": false, "allow_multiple_items": true, - "reviewable": false, "extensions": [] } ] diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 269b47459c..f11621c76e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -72,11 +72,6 @@ "label": "Allow multiple items", "type": "boolean" }, - { - "key": "reviewable", - "label": "Reviewable", - "type": "boolean" - }, { "type": "list", "key": "extensions", From 64b4bdaf880a8b639e921045c4c14a2d6b0cd95b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 15:19:25 +0200 Subject: [PATCH 63/76] handle review representations using reviewable file input --- .../publish/collect_simple_instances.py | 249 +++++++++++------- 1 file changed, 154 insertions(+), 95 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index e8e1c1013c..b4328d948c 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -1,5 +1,4 @@ import os -import json import tempfile import clique @@ -28,28 +27,6 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): hosts = ["traypublisher"] - _image_extensions = [ - ".ani", ".anim", ".apng", ".art", ".bmp", ".bpg", ".bsave", ".cal", - ".cin", ".cpc", ".cpt", ".dds", ".dpx", ".ecw", ".exr", ".fits", - ".flic", ".flif", ".fpx", ".gif", ".hdri", ".hevc", ".icer", - ".icns", ".ico", ".cur", ".ics", ".ilbm", ".jbig", ".jbig2", - ".jng", ".jpeg", ".jpeg-ls", ".jpeg", ".2000", ".jpg", ".xr", - ".jpeg", ".xt", ".jpeg-hdr", ".kra", ".mng", ".miff", ".nrrd", - ".ora", ".pam", ".pbm", ".pgm", ".ppm", ".pnm", ".pcx", ".pgf", - ".pictor", ".png", ".psb", ".psp", ".qtvr", ".ras", - ".rgbe", ".logluv", ".tiff", ".sgi", ".tga", ".tiff", ".tiff/ep", - ".tiff/it", ".ufo", ".ufp", ".wbmp", ".webp", ".xbm", ".xcf", - ".xpm", ".xwd" - ] - _video_extensions = [ - ".3g2", ".3gp", ".amv", ".asf", ".avi", ".drc", ".f4a", ".f4b", - ".f4p", ".f4v", ".flv", ".gif", ".gifv", ".m2v", ".m4p", ".m4v", - ".mkv", ".mng", ".mov", ".mp2", ".mp4", ".mpe", ".mpeg", ".mpg", - ".mpv", ".mxf", ".nsv", ".ogg", ".ogv", ".qt", ".rm", ".rmvb", - ".roq", ".svi", ".vob", ".webm", ".wmv", ".yuv" - ] - _review_extensions = _image_extensions + _video_extensions - def process(self, instance): if not instance.data.get("settings_creator"): return @@ -64,97 +41,133 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): "Created temp staging directory for instance {}. {}" ).format(instance_label, tmp_folder)) - repres = instance.data["representations"] + # Store filepaths for validation of their existence + source_filepaths = [] + # Make sure there are no representations with same name + repre_names_counter = {} + # Store created names for logging + repre_names = [] + # Store set of filepaths per each representation + representation_files_mapping = [] + source = self._create_main_representations( + instance, + source_filepaths, + repre_names_counter, + repre_names, + representation_files_mapping + ) + self._create_review_representation( + instance, + source_filepaths, + repre_names_counter, + repre_names, + representation_files_mapping + ) + + instance.data["source"] = source + instance.data["sourceFilepaths"] = list(set(source_filepaths)) + + self.log.debug( + ( + "Created Simple Settings instance \"{}\"" + " with {} representations: {}" + ).format( + instance_label, + len(instance.data["representations"]), + ", ".join(repre_names) + ) + ) + + def _create_main_representations( + self, + instance, + source_filepaths, + repre_names_counter, + repre_names, + representation_files_mapping + ): creator_attributes = instance.data["creator_attributes"] - self.log.info(json.dumps(creator_attributes)) filepath_items = creator_attributes["filepath"] if not isinstance(filepath_items, list): filepath_items = [filepath_items] - # Last found representation is used as source for instance source = None - # Check if review is enabled and should be created - reviewable = creator_attributes.get("reviewable") - # Store review representation - first found that can be used for - # review is stored - review_representation = None - review_path = None - - # Make sure there are no representations with same name - repre_names_counter = {} - # Store created names for logging - _repre_names = [] - # Store filepaths for validation of their existence - source_filepaths = [] - - # Create representations for filepath_item in filepath_items: - filepaths = [ + # Skip if filepath item does not have filenames + if not filepath_item["filenames"]: + continue + + filepaths = { os.path.join(filepath_item["directory"], filename) for filename in filepath_item["filenames"] - ] + } source_filepaths.extend(filepaths) source = self._calculate_source(filepaths) - filenames = filepath_item["filenames"] - _, ext = os.path.splitext(filenames[0]) - if len(filenames) == 1: - filenames = filenames[0] - - repre_name = repre_ext = ext[1:] - if repre_name not in repre_names_counter: - repre_names_counter[repre_name] = 2 - else: - counter = repre_names_counter[repre_name] - repre_names_counter[repre_name] += 1 - repre_name = "{}_{}".format(repre_name, counter) - - _repre_names.append('"{}"'.format(repre_name)) - representation = { - "ext": repre_ext, - "name": repre_name, - "stagingDir": filepath_item["directory"], - "files": filenames, - "tags": [] - } - repres.append(representation) - - if ( - reviewable - and review_representation is None - and ext in self._review_extensions - ): - review_representation = representation - review_path = source - - instance.data["source"] = source - instance.data["sourceFilepaths"] = source_filepaths - - if reviewable: - self._prepare_review(instance, review_representation, review_path) - - self.log.debug(( - "Created Simple Settings instance \"{}\"" - " with {} representations: {}" - ).format(instance_label, len(repres), ", ".join(_repre_names))) - - def _calculate_source(self, filepaths): - cols, rems = clique.assemble(filepaths) - if cols: - source = cols[0].format("{head}{padding}{tail}") - elif rems: - source = rems[0] + representation = self._create_representation_data( + filepath_item, repre_names_counter, repre_names + ) + instance.data["representations"].append(representation) + representation_files_mapping.append( + (filepaths, representation, source) + ) return source - def _prepare_review(self, instance, review_representation, review_path): - if not review_representation: + def _create_review_representation( + self, + instance, + source_filepaths, + repre_names_counter, + repre_names, + representation_files_mapping + ): + # Skip review representation creation if there are no representations + # created for "main" part + # - review representation must not be created in that case so + # validation can care about it + if not representation_files_mapping: self.log.warning(( - "Didn't find any representation" - " that could be used as source for review" + "There are missing source representations." + " Creation of review representation was skipped." )) return + creator_attributes = instance.data["creator_attributes"] + review_file_item = creator_attributes["reviewable"] + filenames = review_file_item.get("filenames") + if not filenames: + self.log.debug(( + "Filepath for review is not defined." + " Skipping review representation creation." + )) + return + + filepaths = { + os.path.join(review_file_item["directory"], filename) + for filename in filenames + } + source_filepaths.extend(filepaths) + # First try to find out representation with same filepaths + # so it's not needed to create new representation just for review + review_representation = None + # Review path (only for logging) + review_path = None + for item in representation_files_mapping: + _filepaths, representation, repre_path = item + if _filepaths == filepaths: + review_representation = representation + review_path = repre_path + break + + if review_representation is None: + self.log.debug("Creating new review representation") + review_path = self._calculate_source(filepaths) + review_representation = self._create_representation_data( + review_file_item, repre_names_counter, repre_names + ) + instance.data["representations"].append(review_representation) + if "review" not in instance.data["families"]: instance.data["families"].append("review") @@ -162,3 +175,49 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): self.log.debug("Representation {} was marked for review. {}".format( review_representation["name"], review_path )) + + def _create_representation_data( + self, filepath_item, repre_names_counter, repre_names + ): + """Create new representation data based on file item. + + Args: + filepath_item (Dict[str, Any]): Item with information about + representation paths. + repre_names_counter (Dict[str, int]): Store count of representation + names. + repre_names (List[str]): All used representation names. For + logging purposes. + + Returns: + Dict: Prepared base representation data. + """ + + filenames = filepath_item["filenames"] + _, ext = os.path.splitext(filenames[0]) + if len(filenames) == 1: + filenames = filenames[0] + + repre_name = repre_ext = ext[1:] + if repre_name not in repre_names_counter: + repre_names_counter[repre_name] = 2 + else: + counter = repre_names_counter[repre_name] + repre_names_counter[repre_name] += 1 + repre_name = "{}_{}".format(repre_name, counter) + repre_names.append(repre_names) + return { + "ext": repre_ext, + "name": repre_name, + "stagingDir": filepath_item["directory"], + "files": filenames, + "tags": [] + } + + def _calculate_source(self, filepaths): + cols, rems = clique.assemble(filepaths) + if cols: + source = cols[0].format("{head}{padding}{tail}") + elif rems: + source = rems[0] + return source From 1561a4790661dbfe1411b31fe58dcf77cb4bacf7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 15:19:45 +0200 Subject: [PATCH 64/76] changed key 'filepath' to 'representation_files' --- openpype/hosts/traypublisher/api/plugin.py | 2 +- .../traypublisher/plugins/publish/collect_simple_instances.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index cb02a5600e..9b9425855e 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -102,7 +102,7 @@ class SettingsCreator(TrayPublishCreator): def get_instance_attr_defs(self): return [ FileDef( - "filepath", + "representation_files", folders=False, extensions=self.extensions, allow_sequences=self.allow_sequences, diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index b4328d948c..15dac9a4c0 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -88,7 +88,7 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): representation_files_mapping ): creator_attributes = instance.data["creator_attributes"] - filepath_items = creator_attributes["filepath"] + filepath_items = creator_attributes["representation_files"] if not isinstance(filepath_items, list): filepath_items = [filepath_items] From 2b1654a1e24d70c215cbfcc4e8d0922dcb663a9f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 15:22:19 +0200 Subject: [PATCH 65/76] fix variable usage --- .../traypublisher/plugins/publish/collect_simple_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index 15dac9a4c0..a5b95138bd 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -205,7 +205,7 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): counter = repre_names_counter[repre_name] repre_names_counter[repre_name] += 1 repre_name = "{}_{}".format(repre_name, counter) - repre_names.append(repre_names) + repre_names.append(repre_name) return { "ext": repre_ext, "name": repre_name, From 97492867cbcbc528499dc7c0c4490fafa4e945c3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 16:11:01 +0200 Subject: [PATCH 66/76] modified docstring --- .../plugins/publish/collect_simple_instances.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index a5b95138bd..c0ae694c3c 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -8,15 +8,20 @@ import pyblish.api class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): """Collect data for instances created by settings creators. - Plugin create representations based on 'filepath' attribute stored - on instance. + Plugin create representations for simple instances based + on 'representation_files' attribute stored on instance data. + + There is also possibility to have reviewable representation which can be + stored under 'reviewable' attribute stored on instance data. If there was + already created representation with the same files as 'revieable' containes Representations can be marked for review and in that case is also added 'review' family to instance families. For review can be marked only one representation so **first** representation that has extension available in '_review_extensions' is used for review. - For 'source' on instance is used path from last created representation. + For instance 'source' is used path from last representation created + from 'representation_files'. Set staging directory on instance. That is probably never used because each created representation has it's own staging dir. From f346fb8cfd3b59d85b961cba5026cd8e0f66e21a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 17:38:01 +0200 Subject: [PATCH 67/76] implemented helper function for loading of internal data --- .../widgets/attribute_defs/files_widget.py | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/openpype/widgets/attribute_defs/files_widget.py b/openpype/widgets/attribute_defs/files_widget.py index 508da4893b..9b4b1d6dc7 100644 --- a/openpype/widgets/attribute_defs/files_widget.py +++ b/openpype/widgets/attribute_defs/files_widget.py @@ -27,6 +27,20 @@ IS_SEQUENCE_ROLE = QtCore.Qt.UserRole + 7 EXT_ROLE = QtCore.Qt.UserRole + 8 +def convert_bytes_to_json(bytes_value): + if isinstance(bytes_value, QtCore.QByteArray): + # Raw data are already QByteArray and we don't have to load them + encoded_data = bytes_value + else: + encoded_data = QtCore.QByteArray.fromRawData(bytes_value) + stream = QtCore.QDataStream(encoded_data, QtCore.QIODevice.ReadOnly) + text = stream.readQString() + try: + return json.loads(text) + except Exception: + return None + + class SupportLabel(QtWidgets.QLabel): pass @@ -300,17 +314,10 @@ class FilesModel(QtGui.QStandardItemModel): return mime_data def dropMimeData(self, mime_data, action, row, col, index): - internal_move_data = mime_data.data("files_widget/internal_move") - if isinstance(internal_move_data, QtCore.QByteArray): - # Raw data are already QByteArrat and we don't have to load them - encoded_data = internal_move_data - else: - encoded_data = QtCore.QByteArray.fromRawData(internal_move_data) - stream = QtCore.QDataStream(encoded_data, QtCore.QIODevice.ReadOnly) - text = stream.readQString() - try: - item_ids = json.loads(text) - except Exception: + item_ids = convert_bytes_to_json( + mime_data.data("files_widget/internal_move") + ) + if item_ids is None: return False # Find matching item after which will be items moved From ef515039a0a356f2ad5571f20d20976ae799563a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 17:40:01 +0200 Subject: [PATCH 68/76] added helper method for conversion of data to bytes --- openpype/widgets/attribute_defs/files_widget.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/openpype/widgets/attribute_defs/files_widget.py b/openpype/widgets/attribute_defs/files_widget.py index 9b4b1d6dc7..26398020ed 100644 --- a/openpype/widgets/attribute_defs/files_widget.py +++ b/openpype/widgets/attribute_defs/files_widget.py @@ -41,6 +41,13 @@ def convert_bytes_to_json(bytes_value): return None +def convert_data_to_bytes(data): + bytes_value = QtCore.QByteArray() + stream = QtCore.QDataStream(bytes_value, QtCore.QIODevice.WriteOnly) + stream.writeQString(json.dumps(data)) + return bytes_value + + class SupportLabel(QtWidgets.QLabel): pass @@ -306,11 +313,10 @@ class FilesModel(QtGui.QStandardItemModel): index.data(ITEM_ID_ROLE) for index in indexes ] - encoded_data = QtCore.QByteArray() - stream = QtCore.QDataStream(encoded_data, QtCore.QIODevice.WriteOnly) - stream.writeQString(json.dumps(item_ids)) - mime_data = super(FilesModel, self).mimeData(indexes) - mime_data.setData("files_widget/internal_move", encoded_data) + + item_ids_data = convert_data_to_bytes(item_ids) + mime_data = QtCore.QMimeData() + mime_data.setData("files_widget/internal_move", item_ids_data) return mime_data def dropMimeData(self, mime_data, action, row, col, index): From 53dd6cf11159b7732ce81ff05a533dd7e4affaa0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 17:45:39 +0200 Subject: [PATCH 69/76] it is possible to move file items across widgets --- .../widgets/attribute_defs/files_widget.py | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/openpype/widgets/attribute_defs/files_widget.py b/openpype/widgets/attribute_defs/files_widget.py index 26398020ed..98f1d2738a 100644 --- a/openpype/widgets/attribute_defs/files_widget.py +++ b/openpype/widgets/attribute_defs/files_widget.py @@ -225,6 +225,7 @@ class FilesModel(QtGui.QStandardItemModel): def __init__(self, single_item, allow_sequences): super(FilesModel, self).__init__() + self._id = str(uuid.uuid4()) self._single_item = single_item self._multivalue = False self._allow_sequences = allow_sequences @@ -234,6 +235,10 @@ class FilesModel(QtGui.QStandardItemModel): self._filenames_by_dirpath = collections.defaultdict(set) self._items_by_dirpath = collections.defaultdict(list) + @property + def id(self): + return self._id + def set_multivalue(self, multivalue): """Disable filtering.""" @@ -315,8 +320,20 @@ class FilesModel(QtGui.QStandardItemModel): ] item_ids_data = convert_data_to_bytes(item_ids) - mime_data = QtCore.QMimeData() + mime_data = super(FilesModel, self).mimeData(indexes) mime_data.setData("files_widget/internal_move", item_ids_data) + + file_items = [] + for item_id in item_ids: + file_item = self.get_file_item_by_id(item_id) + if file_item: + file_items.append(file_item.to_dict()) + + full_item_data = convert_data_to_bytes({ + "items": file_items, + "id": self._id + }) + mime_data.setData("files_widget/full_data", full_item_data) return mime_data def dropMimeData(self, mime_data, action, row, col, index): @@ -858,6 +875,11 @@ class FilesWidget(QtWidgets.QFrame): event.setDropAction(QtCore.Qt.CopyAction) event.accept() + full_data_value = mime_data.data("files_widget/full_data") + if self._handle_full_data_drag(full_data_value): + event.setDropAction(QtCore.Qt.CopyAction) + event.accept() + def dragLeaveEvent(self, event): event.accept() @@ -868,6 +890,7 @@ class FilesWidget(QtWidgets.QFrame): mime_data = event.mimeData() if mime_data.hasUrls(): event.accept() + # event.setDropAction(QtCore.Qt.CopyAction) filepaths = [] for url in mime_data.urls(): filepath = url.toLocalFile() @@ -879,8 +902,48 @@ class FilesWidget(QtWidgets.QFrame): if filepaths: self._add_filepaths(filepaths) + if self._handle_full_data_drop( + mime_data.data("files_widget/full_data") + ): + event.accept() + event.setDropAction(QtCore.Qt.CopyAction) + + # print(self._files_model.id, event) super(FilesWidget, self).dropEvent(event) + def _handle_full_data_drag(self, value): + if value is None: + return False + + full_data = convert_bytes_to_json(value) + if full_data is None: + return False + + if full_data["id"] == self._files_model.id: + return False + return True + + def _handle_full_data_drop(self, value): + if value is None: + return False + + full_data = convert_bytes_to_json(value) + if full_data is None: + return False + + if full_data["id"] == self._files_model.id: + return False + + for item in full_data["items"]: + filepaths = [ + os.path.join(item["directory"], filename) + for filename in item["filenames"] + ] + filepaths = self._files_proxy_model.filter_valid_files(filepaths) + if filepaths: + self._add_filepaths(filepaths) + return True + def _add_filepaths(self, filepaths): self._files_model.add_filepaths(filepaths) self._update_visibility() From 9a83b83bb53837ffc84fb72f347a5c8875d9f6c1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 17:50:24 +0200 Subject: [PATCH 70/76] it is possible to copy file items --- openpype/widgets/attribute_defs/files_widget.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/openpype/widgets/attribute_defs/files_widget.py b/openpype/widgets/attribute_defs/files_widget.py index 98f1d2738a..d29aa1b607 100644 --- a/openpype/widgets/attribute_defs/files_widget.py +++ b/openpype/widgets/attribute_defs/files_widget.py @@ -905,10 +905,9 @@ class FilesWidget(QtWidgets.QFrame): if self._handle_full_data_drop( mime_data.data("files_widget/full_data") ): - event.accept() event.setDropAction(QtCore.Qt.CopyAction) + event.accept() - # print(self._files_model.id, event) super(FilesWidget, self).dropEvent(event) def _handle_full_data_drag(self, value): @@ -942,8 +941,19 @@ class FilesWidget(QtWidgets.QFrame): filepaths = self._files_proxy_model.filter_valid_files(filepaths) if filepaths: self._add_filepaths(filepaths) + + if self._copy_modifiers_enabled(): + return False return True + def _copy_modifiers_enabled(self): + if ( + QtWidgets.QApplication.keyboardModifiers() + & QtCore.Qt.ControlModifier + ): + return True + return False + def _add_filepaths(self, filepaths): self._files_model.add_filepaths(filepaths) self._update_visibility() From e0fe2e84b5225a47e9c64472a91d74691d46f5dc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 18:22:48 +0200 Subject: [PATCH 71/76] remove unnecessary line edit --- openpype/widgets/attribute_defs/widgets.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py index e4c4aba170..d0ba8814c7 100644 --- a/openpype/widgets/attribute_defs/widgets.py +++ b/openpype/widgets/attribute_defs/widgets.py @@ -379,11 +379,6 @@ class EnumAttrWidget(_BaseAttrDefWidget): combo_delegate = QtWidgets.QStyledItemDelegate(input_widget) input_widget.setItemDelegate(combo_delegate) - line_edit = QtWidgets.QLineEdit(input_widget) - line_edit.setReadOnly(True) - line_edit.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents) - input_widget.setLineEdit(line_edit) - if self.attr_def.tooltip: input_widget.setToolTip(self.attr_def.tooltip) From baa1256b380630b0d23f4a57ed34431e947d4c85 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Jul 2022 11:22:22 +0200 Subject: [PATCH 72/76] added settings to define when cycle review session creation happens --- .../defaults/project_settings/ftrack.json | 5 ++++ .../schema_project_ftrack.json | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index 831c34835e..b102b340be 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -124,6 +124,11 @@ "Project Manager" ], "cycle_enabled": false, + "cycle_hour_start": [ + 0, + 0, + 0 + ], "review_session_template": "{yy}{mm}{dd}" } }, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json index c0069dcdab..4119184ca9 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json @@ -412,6 +412,31 @@ "key": "cycle_enabled", "label": "Create daily review session" }, + { + "type": "list-strict", + "key": "cycle_hour_start", + "label": "Create daily review session at", + "tooltip": "This may take affect on next day", + "object_types": [ + { + "label": "HMS", + "type": "number", + "minimum": 0, + "maximum": 23, + "decimal": 0 + }, { + "type": "number", + "minimum": 0, + "maximum": 59, + "decimal": 0 + }, { + "type": "number", + "minimum": 0, + "maximum": 59, + "decimal": 0 + } + ] + }, { "type": "separator" }, From beb53f7ccb513cd37a54a19b366ecccf120b8605 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Jul 2022 11:23:07 +0200 Subject: [PATCH 73/76] use time settings to determine when to trigger creation --- .../action_create_review_session.py | 63 ++++++++++++------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py b/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py index 8a8e86e7b9..68d498a83a 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py +++ b/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py @@ -6,7 +6,10 @@ import collections import ftrack_api from openpype.lib import get_datetime_data -from openpype.api import get_project_settings +from openpype.settings.lib import ( + get_project_settings, + get_default_project_settings +) from openpype_modules.ftrack.lib import ServerAction @@ -79,6 +82,35 @@ class CreateDailyReviewSessionServerAction(ServerAction): ) return True + def _calculate_next_cycle_delta(self): + studio_default_settings = get_default_project_settings() + action_settings = ( + studio_default_settings + ["ftrack"] + [self.settings_frack_subkey] + [self.settings_key] + ) + cycle_hour_start = action_settings.get("cycle_hour_start") + if not cycle_hour_start: + h = m = s = 0 + else: + h, m, s = cycle_hour_start + + # Create threading timer which will trigger creation of report + # at the 00:00:01 of next day + # - callback will trigger another timer which will have 1 day offset + now = datetime.datetime.now() + # Create object of today morning + expected_next_trigger = datetime.datetime( + now.year, now.month, now.day, h, m, s + ) + if expected_next_trigger > now: + seconds = (expected_next_trigger - now).total_seconds() + else: + expected_next_trigger += self._day_delta + seconds = (expected_next_trigger - now).total_seconds() + return seconds, expected_next_trigger + def register(self, *args, **kwargs): """Override register to be able trigger """ # Register server action as would be normally @@ -86,22 +118,12 @@ class CreateDailyReviewSessionServerAction(ServerAction): *args, **kwargs ) - # Create threading timer which will trigger creation of report - # at the 00:00:01 of next day - # - callback will trigger another timer which will have 1 day offset - now = datetime.datetime.now() - # Create object of today morning - today_morning = datetime.datetime( - now.year, now.month, now.day, 0, 0, 1 - ) - # Add a day delta (to calculate next day date) - next_day_morning = today_morning + self._day_delta - # Calculate first delta in seconds for first threading timer - first_delta = (next_day_morning - now).total_seconds() + seconds_delta, cycle_time = self._calculate_next_cycle_delta() + # Store cycle time which will be used to create next timer - self._last_cyle_time = next_day_morning + self._last_cyle_time = cycle_time # Create timer thread - self._cycle_timer = threading.Timer(first_delta, self._timer_callback) + self._cycle_timer = threading.Timer(seconds_delta, self._timer_callback) self._cycle_timer.start() self._check_review_session() @@ -111,13 +133,12 @@ class CreateDailyReviewSessionServerAction(ServerAction): self._cycle_timer is not None and self._last_cyle_time is not None ): - now = datetime.datetime.now() - while self._last_cyle_time < now: - self._last_cyle_time = self._last_cyle_time + self._day_delta + seconds_delta, cycle_time = self._calculate_next_cycle_delta() + self._last_cyle_time = cycle_time - delay = (self._last_cyle_time - now).total_seconds() - - self._cycle_timer = threading.Timer(delay, self._timer_callback) + self._cycle_timer = threading.Timer( + seconds_delta, self._timer_callback + ) self._cycle_timer.start() self._check_review_session() From e74f526def58d48fe5d77c44ea5b3615d595da4b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Jul 2022 11:42:48 +0200 Subject: [PATCH 74/76] modified labels --- .../projects_schema/schema_project_ftrack.json | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json index 4119184ca9..e008fd85ee 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json @@ -410,7 +410,10 @@ { "type": "boolean", "key": "cycle_enabled", - "label": "Create daily review session" + "label": "Run automatically every day" + }, + { + "type": "separator" }, { "type": "list-strict", @@ -419,17 +422,19 @@ "tooltip": "This may take affect on next day", "object_types": [ { - "label": "HMS", + "label": "H:", "type": "number", "minimum": 0, "maximum": 23, "decimal": 0 }, { + "label": "M:", "type": "number", "minimum": 0, "maximum": 59, "decimal": 0 }, { + "label": "S:", "type": "number", "minimum": 0, "maximum": 59, @@ -437,6 +442,10 @@ } ] }, + { + "type": "label", + "label": "This can't be overriden per project and any change will take effect on the next day or on restart of event server." + }, { "type": "separator" }, From af06e1a8511979ccc1398f1fda9887c98289f0ee Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Jul 2022 12:02:19 +0200 Subject: [PATCH 75/76] fix too long line --- .../event_handlers_server/action_create_review_session.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py b/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py index 68d498a83a..21382007a0 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py +++ b/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py @@ -123,7 +123,9 @@ class CreateDailyReviewSessionServerAction(ServerAction): # Store cycle time which will be used to create next timer self._last_cyle_time = cycle_time # Create timer thread - self._cycle_timer = threading.Timer(seconds_delta, self._timer_callback) + self._cycle_timer = threading.Timer( + seconds_delta, self._timer_callback + ) self._cycle_timer.start() self._check_review_session() From 870cc2bc0607b36328f03c2b75be4c5b12e39d6e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Jul 2022 13:34:26 +0200 Subject: [PATCH 76/76] fix hash of oiio centos file --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4bdaaab4ed..078503a284 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -135,7 +135,7 @@ hash = "b9950f5d2fa3720b52b8be55bacf5f56d33f9e029d38ee86534995f3d8d253d2" [openpype.thirdparty.oiio.linux] url = "https://distribute.openpype.io/thirdparty/oiio_tools-2.2.20-linux-centos7.tgz" -hash = "be1abf8a50e9da5913298447421af0a17829d83ed6252ae1d40da7fa36a78787" +hash = "3894dec7e4e521463891a869586850e8605f5fd604858b674c87323bf33e273d" [openpype.thirdparty.oiio.darwin] url = "https://distribute.openpype.io/thirdparty/oiio-2.2.0-darwin.tgz"